feat(backup): async restore family — no proxy-timeout error page on a succeeding restore (v0.102.0)
Re-adjudicates F4: /backup/restore, /backup/tier2/restore, /backup/offbox/restore blocked the HTTP request until completion, so through cloudflared's 100s cap a customer got an error page while the restore succeeded (offbox worse — bounded on r.Context(), canceling the SFTP restore mid-flight). Convert all three to the offboxRun async shape: fast-path IsRunning refuse, background goroutine (offbox ctx off r.Context() -> Background+30m), instant redirect. Add mutex- guarded op-status (opstatus.go) + GET /api/backup/restore-status + a 3s-polling backups.html banner (neutral running, red on failure). Restore single-flight unchanged. Tests + red-proof (sync handler blocks indefinitely vs <500ms async). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -134,13 +134,25 @@ func (s *Server) offboxRestoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||
offboxRedirect(w, r, "Hiányzó alkalmazás.", true)
|
||||
return
|
||||
}
|
||||
dest := filepath.Join(s.cfg.Paths.DataDir, "offbox-restore", app)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Minute)
|
||||
defer cancel()
|
||||
if err := s.backupMgr.RestoreOffbox(ctx, app, dest); err != nil {
|
||||
s.logger.Printf("[ERROR] [web] off-box restore %s: %v", app, err)
|
||||
offboxRedirect(w, r, "A visszaállítás sikertelen: "+err.Error(), true)
|
||||
// Part B: fast-path refuse a concurrent op, then run async on a BACKGROUND context. The old code
|
||||
// bounded on r.Context()+30m — a proxy read-timeout then CANCELED the SFTP restore mid-flight
|
||||
// (worse than F4: not just an error page, an aborted restore). Background ctx fixes that.
|
||||
if s.backupMgr.IsRunning() {
|
||||
offboxRedirect(w, r, "Egy mentési/visszaállítási művelet már fut.", true)
|
||||
return
|
||||
}
|
||||
offboxRedirect(w, r, "A(z) "+app+" visszaállítva ide (ellenőrzésre): "+dest, false)
|
||||
dest := filepath.Join(s.cfg.Paths.DataDir, "offbox-restore", app)
|
||||
s.backupMgr.BeginRestoreOp("offbox-restore", app)
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
defer cancel()
|
||||
if err := s.backupMgr.RestoreOffbox(ctx, app, dest); err != nil {
|
||||
s.logger.Printf("[ERROR] [web] off-box restore %s (async): %v", app, err)
|
||||
s.backupMgr.EndRestoreOp(false, "A visszaállítás sikertelen: "+err.Error())
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] off-box restore %s completed (async) → %s", app, dest)
|
||||
s.backupMgr.EndRestoreOp(true, "A(z) "+app+" visszaállítva ide (ellenőrzésre): "+dest)
|
||||
}()
|
||||
offboxRedirect(w, r, "A NAS-visszaállítás elindult — az állapot itt frissül.", false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user