Documentation / guides
Shutdown
Close Session resources in the runtime-defined order.
SessionController.Shutdown is the owned teardown transaction. It closes
admission, reaches every registered loop, joins resources and durable event
publication, then releases workspace and session ownership. It is safe to call
from more than one goroutine, but every caller should still use one shared
controller lifetime.
Shutdown contract
func (s SessionController) Shutdown(context.Context) error
The first caller becomes the teardown owner. Later callers wait for the same
cleanup result. A nil context is treated as context.Background() by the
runtime. Shutdown returns a *session.SessionError with
SessionContextDone when its own cleanup error or the caller’s context error
must be reported; the cleanup itself is not detached when a caller cancels.
Teardown order
The order is load-bearing:
- Serialize with active-loop selection, latch
closing, and snapshot every registered loop under the same lock used byNewLoopregistration. - Revoke collaboration origins, stop permission reviews, close hustle
admission, and send
command.Shutdownto every loop in the snapshot. - Wait for each reached loop’s acknowledgement and actor drain, then join hustle terminal audit, finalizers, and blocking activity.
- Close collaboration broker, stop offload GC, stop checkpoints, and fully shut down session resources while the hub and journal are still live.
- Stop the hub, which appends/delivers
SessionStoppedbefore it stops publication. - Release the exclusive workspace root lease, then the session lease, and
cancel
sessionCtxlast.
%%{init: {"theme":"dark"}}%%
flowchart TD
A[Shutdown owner] --> B[Latch closing and snapshot loops]
B --> C[Revoke origins and close hustle admission]
C --> D[Send Shutdown to every loop]
D --> E[Wait loop acks and drains]
E --> F[Join broker, GC, checkpoints, resources]
F --> G[Append SessionStopped and stop hub]
G --> H[Release workspace root lease]
H --> I[Release session lease]
I --> J[Cancel session context]
Concurrent callers
Only one cleanup owner runs. A second call waits on shutdownDone, then sees
the same aggregate cleanup result. If the second caller’s context is already
cancelled, its context error is combined after shared cleanup completes; it
does not interrupt or reorder the first caller’s teardown.
var wg sync.WaitGroup
errs := make(chan error, 2)
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
errs <- controller.Shutdown(context.Background())
}()
}
wg.Wait()
close(errs)
for err := range errs {
if err != nil {
return err
}
}
Caller context
Loop, checkpoint, hub, and durable-I/O phases receive private deadlines based
on validated component bounds. The caller context is diagnostic at the outer
boundary. Lease release uses fresh bounded contexts and is attempted exactly
once. A shutdown error can be a single underlying typed error or a
shutdownErrorSet joined error; use errors.As to inspect each cause.
if err := controller.Shutdown(ctx); err != nil {
var se *session.SessionError
if errors.As(err, &se) {
log.Printf("shutdown session error: %v", se)
}
return err
}
After shutdown, submits and active-loop changes fail closed, and no new loop
can be registered. The durable SessionStopped event remains in history for
the next restore attempt.