mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-09-05 23:47:33 +08:00
refactor: simplify architecture and harden lifecycle
Remove obsolete implementations, centralize background task ownership and terminal-state recovery, consolidate frontend routing and log streaming, and enforce project-wide verification in CI.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Supervisor owns application background tasks. It rejects new work once
|
||||
// shutdown starts, cancels the shared task context, and waits for accepted work.
|
||||
type Supervisor struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
mu sync.Mutex
|
||||
stopping bool
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func NewSupervisor(parent context.Context) *Supervisor {
|
||||
if parent == nil {
|
||||
parent = context.Background()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
return &Supervisor{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Context is the root context passed to every accepted task.
|
||||
func (s *Supervisor) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
// Go starts task unless shutdown has begun or the root context is already
|
||||
// canceled. The lock makes Add and the transition to Wait mutually exclusive.
|
||||
func (s *Supervisor) Go(task func(context.Context)) bool {
|
||||
if s == nil || task == nil {
|
||||
return false
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.stopping || s.ctx.Err() != nil {
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
s.wg.Add(1)
|
||||
s.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
task(s.ctx)
|
||||
}()
|
||||
return true
|
||||
}
|
||||
|
||||
// Shutdown is idempotent. Cancellation always happens, even when waitCtx has
|
||||
// already expired; callers may call Shutdown again to wait for eventual exit.
|
||||
func (s *Supervisor) Shutdown(waitCtx context.Context) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if waitCtx == nil {
|
||||
waitCtx = context.Background()
|
||||
}
|
||||
s.stopOnce.Do(func() {
|
||||
s.mu.Lock()
|
||||
s.stopping = true
|
||||
s.cancel()
|
||||
s.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
s.wg.Wait()
|
||||
close(s.done)
|
||||
}()
|
||||
})
|
||||
|
||||
select {
|
||||
case <-s.done:
|
||||
return nil
|
||||
case <-waitCtx.Done():
|
||||
return waitCtx.Err()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSupervisorShutdownCancelsAndWaits(t *testing.T) {
|
||||
supervisor := NewSupervisor(context.Background())
|
||||
started := make(chan struct{})
|
||||
finished := make(chan struct{})
|
||||
if !supervisor.Go(func(ctx context.Context) {
|
||||
close(started)
|
||||
<-ctx.Done()
|
||||
close(finished)
|
||||
}) {
|
||||
t.Fatal("expected task to be accepted")
|
||||
}
|
||||
<-started
|
||||
|
||||
waitCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
if err := supervisor.Shutdown(waitCtx); err != nil {
|
||||
t.Fatalf("Shutdown returned error: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-finished:
|
||||
default:
|
||||
t.Fatal("Shutdown returned before the task finished")
|
||||
}
|
||||
if supervisor.Go(func(context.Context) {}) {
|
||||
t.Fatal("expected task submitted after shutdown to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupervisorShutdownHonorsWaitContext(t *testing.T) {
|
||||
supervisor := NewSupervisor(context.Background())
|
||||
release := make(chan struct{})
|
||||
if !supervisor.Go(func(context.Context) { <-release }) {
|
||||
t.Fatal("expected task to be accepted")
|
||||
}
|
||||
|
||||
waitCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
if err := supervisor.Shutdown(waitCtx); !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("Shutdown error = %v, want context.Canceled", err)
|
||||
}
|
||||
close(release)
|
||||
if err := supervisor.Shutdown(context.Background()); err != nil {
|
||||
t.Fatalf("second Shutdown returned error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user