| index 17615c317..e47ef06d5 100644 |
| --- a/internal/activator/activator.go |
| +++ b/internal/activator/activator.go |
| @@ -117,14 +117,19 @@ func (c Config) hibernatePrefix(host string) string { |
| // wake coalescing, idle→sleep, and lease-lost stand-down need no AWS. |
| |
| // Backend is the side-effect seam (Eleven's actBackend). ExecBackend is the |
| -// production implementation. |
| +// production implementation for exec-child instances; UnitBackend drives |
| +// carlos-app@ unit tenants; SplitBackend composes the two by the route's |
| +// Backing. Every method takes the full route (Eleven passed bare host/dbPath |
| +// strings) precisely so that composition can dispatch without a registry |
| +// read of its own. |
| type Backend interface { |
| // Acquire claims the per-host single-writer lease. Contention surfaces as |
| // errLeaseHeld so the edge can answer 503 + Retry-After. |
| - Acquire(ctx context.Context, host string) (Lease, error) |
| - // Restore materializes the local DB from S3 when it's absent (a no-op when |
| - // the warm-cache copy is still on disk). |
| - Restore(ctx context.Context, host, dbPath string) error |
| + Acquire(ctx context.Context, rt registry.Route) (Lease, error) |
| + // Restore materializes the local DB (rt.DBPath; the backend defaults it) |
| + // from S3 when it's absent (a no-op when the warm-cache copy is still on |
| + // disk). |
| + Restore(ctx context.Context, rt registry.Route) error |
| // Start spawns the instance process and waits until its socket accepts. It |
| // also returns the build it execed (the live/<host> symlink target read at |
| // spawn), so the sweep can later spot a repoint under a still-running child |
| @@ -135,8 +140,9 @@ type Backend interface { |
| // delivery (unix seconds; 0 = nothing scheduled). Queried at sleep time, |
| // while the socket still answers, to feed the due-time index. |
| NextDue(ctx context.Context, rt registry.Route) (int64, error) |
| - // HasSidecar reports whether host has a sidecar staged on this box. |
| - HasSidecar(host string) bool |
| + // HasSidecar reports whether the route's host has a sidecar staged on |
| + // this box. |
| + HasSidecar(rt registry.Route) bool |
| // StartSidecar spawns the host's sidecar. Fire-and-forget: a sidecar is a |
| // pure outbound client with no socket, so there's nothing to health-check — |
| // its own connect/backoff loop recovers from a slow start. |
| @@ -597,16 +603,12 @@ type wokenInstance struct { |
| |
| func (a *Service) doWake(ctx context.Context, rt registry.Route) (*wokenInstance, error) { |
| host := rt.Host |
| - // The DB path rides the route; when empty the backend defaults it to |
| - // <DataDir>/<host>.db internally (Eleven defaulted here via a package |
| - // global — base dirs are backend fields now). |
| - dbPath := rt.DBPath |
| |
| // 1. The lease first: everything after this line is done as the prefix's |
| // single writer. Contention is not retried here — on one box it means a |
| // previous incarnation hasn't released; tell the client to come back after |
| // the TTL instead of stalling its request. |
| - lease, err := a.backend.Acquire(ctx, host) |
| + lease, err := a.backend.Acquire(ctx, rt) |
| if err != nil { |
| var held *errLeaseHeld |
| if errors.As(err, &held) { |
| @@ -621,7 +623,7 @@ func (a *Service) doWake(ctx context.Context, rt registry.Route) (*wokenInstance |
| // S3 state written by the true holder). When it's still on disk it is the |
| // warm cache: on one box, with the lease held, the local copy can only be |
| // what we ourselves checkpointed at the last sleep. |
| - if err := a.backend.Restore(ctx, host, dbPath); err != nil { |
| + if err := a.backend.Restore(ctx, rt); err != nil { |
| _ = lease.Release(ctx) |
| return nil, fmt.Errorf("restore %s: %w", host, err) |
| } |
| @@ -638,7 +640,10 @@ func (a *Service) doWake(ctx context.Context, rt registry.Route) (*wokenInstance |
| |
| // 4. Trade the held lease for running replication, at the route's |
| // replication-class sync interval (the per-plan durability ladder). |
| - rep, err := lease.Replicate(ctx, dbPath, replicationSyncInterval(rt.Replication)) |
| + // The DB path rides the route; when empty the backend defaults it to |
| + // <DataDir>/<host>.db internally (Eleven defaulted here via a package |
| + // global — base dirs are backend fields now). |
| + rep, err := lease.Replicate(ctx, rt.DBPath, replicationSyncInterval(rt.Replication)) |
| if err != nil { |
| stopCtx, cancel := context.WithTimeout(context.Background(), actStopTimeout) |
| defer cancel() |
| @@ -653,7 +658,7 @@ func (a *Service) doWake(ctx context.Context, rt registry.Route) (*wokenInstance |
| // failure is loud but never fails the wake: members come before bots, and |
| // the sweeper retries with backoff. |
| w := &wokenInstance{rep: rep, proc: proc, spawnVersion: spawnVer} |
| - if a.backend.HasSidecar(host) { |
| + if a.backend.HasSidecar(rt) { |
| w.sidecarWanted = true |
| if sc, err := a.backend.StartSidecar(ctx, rt); err != nil { |
| log.Printf("activator: SIDECAR for %s failed to start: %v — serving without it (the sweep retries)", host, err) |
| @@ -844,7 +849,7 @@ func (a *Service) sweepOnce() { |
| // only hibernating hosts are activator children and their systemd |
| // sidecar units stay disabled — the staged sidecar has exactly one |
| // possible runner. |
| - if !e.sidecarWanted && a.backend.HasSidecar(e.host) { |
| + if !e.sidecarWanted && a.backend.HasSidecar(e.route) { |
| e.sidecarWanted = true |
| log.Printf("activator: sidecar staged for awake %s — adopting", e.host) |
| } |