concurrency
Graceful shutdown: WaitGroup vs JoinHandle + select
A periodic background worker that advances paid orders through the fulfillment chain. SIGTERM must stop the loop without aborting an in-flight transition. Go uses context.Context + sync.WaitGroup; Rust uses tokio::select! on a oneshot signal plus an awaited JoinHandle.
// shop-two-backends not found at build time // shop-two-backends not found at build time // shop-two-backends not found at build time // shop-two-backends not found at build time What to take away
The shape of graceful shutdown is the same on both sides: stop accepting new work, finish the work in flight, then exit. The plumbing is where they diverge.
Go threads context.Context as
the cancellation signal — the same context the HTTP layer uses,
cancelled by signal.NotifyContext on SIGTERM. The
worker selects on ctx.Done() at the loop boundary.
For the actual DB work inside a tick, the worker uses a
*separate* context.Background() with its own timeout
so an arriving signal mid-transaction doesn't roll back a
partial commit. sync.WaitGroup is the join: main
Wait()s after srv.Shutdown returns.
Rust doesn't have a "context" abstraction.
The pattern is tokio::select! on the cancellation
future (&mut shutdown_rx) versus the work
future (ticker.tick()). The work future is awaited
to completion inside its branch — a shutdown that arrives during
a tick is observed at the *next* select boundary, after the
current transaction commits. JoinHandle::await is
the join: main awaits the worker after
axum::serve returns.
Two practical notes:
- Decouple the signal from the work context.
On both sides. Cancellation should stop the *loop*, not abort
the database transaction the loop kicked off. The Go side does
this with a derived
context.Background()per tick; the Rust side does it implicitly becauseselect!only fires on branch boundaries, not in the middle of an awaited future. - Order: drain HTTP, then the worker.
Reverse it and you'll have the worker writing to an order the
HTTP layer is still racing to update. Both
main.rsandmain.gohere serve the HTTP shutdown first, then signal the worker.
Verdict: this is one of the few pages where the two languages
land at roughly equal weight. Go's context-passing is uniform;
Rust's select! is a direct, allocation-free way to
fan in cancellation. Pick whichever idiom your team finds easier
to review.