Repository navigation
main, fuseadapter: stop gracefully on SIGTERM/SIGINT and detach stale FUSE mounts #1273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
e573cb3
main, fuseadapter: stop on SIGTERM/SIGINT and detach stale FUSE mounts
claude d75aca7
docs: filesystem guide reflects signal shutdown and stale-mount recovery
claude 90e339d
main, fuseadapter: address review findings on signal stop and stale m…
claude f541436
fuseadapter, main: bound pre-mount work by the run context, resolve r…
claude c784758
fuseadapter, main: keep non-Linux builds, drain on filesystem startup…
claude ea0c2a4
fuseadapter, main: bound the liveness probe and never hang a signal s…
claude bf34868
main, fuseadapter: bound the unmount itself and keep symlink resoluti…
claude 01a3f73
main: never let the cleanup entry re-wait on a stuck FUSE unmount
claude 432b09e
main, fuseadapter: one shutdown budget, cancellable kernel mount, ear…
claude d3ce3d4
fuseadapter: serve before unmounting a mount abandoned by a cancelled…
claude 43e3e64
fuseadapter: keep the kernel detach fallback reachable when a fusermo…
claude 128cf37
docs: point the stale-mount note at startup step 3
claude 909090e
main: bound gRPC graceful stop and keep the serve-loop wait after a h…
claude 9dcb445
docs: take main's README and filesystem guide before merging main
bootjp 5a559a4
Merge branch 'main' into feature/modest-knuth-934a9a
bootjp a96874f
docs: filesystem guide and README describe signal stop and stale-moun…
bootjp 9c2f9db
monitoring, adapter: bound HTTP shutdown and close what a stalled req…
bootjp 8bbdead
adapter: SQS stop drains the HTTP listener under the shutdown bound
bootjp 55c9336
adapter: bound the Redis shutdown wait and refuse commands once it began
bootjp 59ae4ec
adapter: S3 stop drains HTTP and admin object operations before closi…
bootjp 02a6201
adapter: count admin S3 object operations so Stop keeps the blob clus…
bootjp 5687da4
adapter: join the SQS reaper and sweep workers before Stop returns
bootjp 639f4b6
fuseadapter: detach only the server's own FUSE type, named after the …
bootjp de16949
main: widen the serve-loop timing margin and pin that Serve returns o…
bootjp 5efa169
main: bound the admin listener shutdown and answer a stopping S3 node…
bootjp 6c60784
fuseadapter: recheck the mount before trying the second detach helper
bootjp 57241ff
fuseadapter: let a cancelled startup end the post-mount readiness wait
bootjp eba7347
fuseadapter: bound the mount-helper lookup by the detach budget
bootjp 38965a9
fuseadapter: recheck the mount right before every detach helper
bootjp bea9aae
adapter: join the S3 background cleanups before Stop closes the blob …
bootjp 283bc3f
adapter: join the S3 cleanups Stop cancelled before closing the blob …
bootjp ce411b7
docs: note that a stop during the store open needs SIGKILL if the ope…
bootjp 4a4c47c
main: cancel before ending the serve loop in the cleanup-bound lifecy…
bootjp bbe0367
adapter: count admin object deletes before Stop closes the cleanup gate
bootjp 816a0d1
fuseadapter: cancel in-flight requests when the shutdown detaches lazily
bootjp 8cf7c48
adapter: join DynamoDB table cleanups before Stop returns
bootjp 14ad332
main: cancel on the first shutdown signal before logging it
bootjp b152648
main: restore main.go content
bootjp 512fc52
adapter: mark ErrAdminS3Stopping as not-leader ahead of the main merge
bootjp 0b845d1
Merge branch 'main' into feature/modest-knuth-934a9a
bootjp 5030ad7
fuseadapter: release a cancelled startup mount inside one window
bootjp 66d7406
adapter: reserve a table cleanup before the delete that needs it commits
bootjp 63bc505
fuseadapter: cancel and bound the release of a late or overdue mount
bootjp 9ecaca9
adapter: let concurrent Stop calls share one drain and join deadline
bootjp bab4b5c
adapter: answer a stopping DynamoDB admin request as not-leader
bootjp 69663e5
adapter: wrap the stopping sentinel at the table delete's return site
bootjp c1c7631
main: bound the wait for the signal handler goroutine on shutdown
bootjp 3eb6989
adapter: join handlers on force-closed HTTP connections before Stop r…
bootjp 86bca21
adapter: preserve cleanup reservations during shutdown
bootjp 7f45508
Merge remote-tracking branch 'origin/main' into feature/pr1273-review…
bootjp 7f7a74d
test: make FUSE detach helper markers portable
bootjp 8b2f97b
Merge remote-tracking branch 'origin/main' into feature/pr1273-review…
bootjp a35bb91
main: join shutdown handlers and reap unmount helpers
bootjp 6733614
test: supply contexts to admin HTTP request fixtures
bootjp 8c5ad96
fuseadapter: reap startup unmount helpers before detach
bootjp 4bc0239
Merge main while retaining shutdown and SQS audit state
bootjp 95c62a6
Join late mount cleanup and close expired cleanup reservations
bootjp cd05729
Merge remote-tracking branch 'origin/main' into feature/pr1273-review…
bootjp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package adapter | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestBackgroundReservationStartsLiveAfterShutdownBound(t *testing.T) { | ||
| w := newBackgroundWorkers() | ||
| r, ok := w.reserve() | ||
| require.True(t, ok) | ||
| done := make(chan struct{}) | ||
| go func() { w.stop("test", time.Millisecond, time.Second); close(done) }() | ||
| <-w.ctx.Done() | ||
| ran := make(chan error, 1) | ||
| r.start(time.Second, func(ctx context.Context) { ran <- ctx.Err() }) | ||
| require.NoError(t, <-ran) | ||
| select { | ||
| case <-done: | ||
| case <-time.After(time.Second): | ||
| t.Fatal("stop did not join reserved cleanup") | ||
| } | ||
| } | ||
|
|
||
| func TestBackgroundReservationCannotStartAfterStopReturns(t *testing.T) { | ||
| w := newBackgroundWorkers() | ||
| r, ok := w.reserve() | ||
| require.True(t, ok) | ||
| w.stop("test", time.Millisecond, time.Millisecond) | ||
| ran := make(chan struct{}, 1) | ||
| r.start(time.Second, func(context.Context) { ran <- struct{}{} }) | ||
| r.release() | ||
| require.Zero(t, w.gate.remaining()) | ||
| select { | ||
| case <-ran: | ||
| t.Fatal("cleanup started after shutdown returned") | ||
| case <-time.After(time.Millisecond): | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| package adapter | ||
|
|
||
| import ( | ||
| "context" | ||
| "log/slog" | ||
| "sync" | ||
| "time" | ||
| ) | ||
|
|
||
| // backgroundWorkers runs the jobs a request starts but does not wait for, | ||
| // such as the cleanup of a deleted table's old generation. Each job is | ||
| // counted in an inflightGate so the server's Stop can join it, bounded, | ||
| // before the stores close underneath it. Stop cancels jobs already started | ||
| // once the bound has passed; reservations that commit later get a live | ||
| // timeout and stay counted in the final join. Reservations cannot start | ||
| // cleanup after that final join ends. This bounded drain does not provide | ||
| // durable recovery of cleanup for mutations that outlive shutdown. | ||
| // A nil *backgroundWorkers | ||
| // runs every job detached under context.Background() and has nothing to | ||
| // stop, which keeps zero-value servers in tests working. | ||
| type backgroundWorkers struct { | ||
| gate *inflightGate | ||
| ctx context.Context | ||
| cancel context.CancelFunc | ||
| mu sync.Mutex | ||
| running map[*backgroundReservation]context.CancelFunc | ||
| closed bool | ||
| } | ||
|
|
||
| func newBackgroundWorkers() *backgroundWorkers { | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| return &backgroundWorkers{gate: newInflightGate(), ctx: ctx, cancel: cancel, running: make(map[*backgroundReservation]context.CancelFunc)} | ||
| } | ||
|
|
||
| // backgroundReservation is a job slot claimed before the commit that makes | ||
| // the job necessary, so the job can still start after that commit even when | ||
| // stop began in between: stop waits for the slot as for a running job. The | ||
| // holder either starts the job through it or releases it. | ||
| type backgroundReservation struct { | ||
| mu sync.Mutex | ||
| w *backgroundWorkers | ||
| used bool | ||
| } | ||
|
|
||
| // reserve claims a job slot, or reports false once stop has begun, in which | ||
| // case the caller must not commit the change the job would follow up on. | ||
| func (w *backgroundWorkers) reserve() (*backgroundReservation, bool) { | ||
| if w != nil && !w.gate.enter() { | ||
| return nil, false | ||
| } | ||
| return &backgroundReservation{w: w}, true | ||
| } | ||
|
|
||
| // start runs run on its own goroutine under a context bounded by timeout, | ||
| // handing it the reserved slot. A reservation starts at most one job. | ||
| func (r *backgroundReservation) start(timeout time.Duration, run func(ctx context.Context)) { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
| if r.used { | ||
| return | ||
| } | ||
| r.used = true | ||
| ctx, cancel := context.WithTimeout(context.Background(), timeout) | ||
| if r.w != nil { | ||
| r.w.mu.Lock() | ||
| if r.w.closed { | ||
| r.w.mu.Unlock() | ||
| cancel() | ||
| r.w.gate.exit() | ||
| return | ||
| } | ||
| r.w.running[r] = cancel | ||
| r.w.mu.Unlock() | ||
| } | ||
| go func() { | ||
| defer cancel() | ||
| if r.w != nil { | ||
| defer r.w.gate.exit() | ||
| defer func() { | ||
| r.w.mu.Lock() | ||
| delete(r.w.running, r) | ||
| r.w.mu.Unlock() | ||
| }() | ||
| } | ||
| run(ctx) | ||
| }() | ||
| } | ||
|
|
||
| // release gives back a slot no job was started through; it is a no-op once | ||
| // start ran or after an earlier release. | ||
| func (r *backgroundReservation) release() { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
| if r.used { | ||
| return | ||
| } | ||
| r.used = true | ||
| if r.w != nil { | ||
| r.w.gate.exit() | ||
| } | ||
| } | ||
|
|
||
| // stop refuses new reservations, waits up to timeout for the reserved slots | ||
| // and the running jobs, then cancels the context of any jobs that remain and | ||
| // waits up to joinTimeout for them to return, so none is still dispatching | ||
| // when the stores close. A call made while an earlier one is still waiting | ||
| // waits until the same deadlines (see inflightGate); name labels the | ||
| // warnings it logs. | ||
| func (w *backgroundWorkers) stop(name string, timeout, joinTimeout time.Duration) { | ||
| if w == nil { | ||
| return | ||
| } | ||
| defer w.closeReservations() | ||
| finished := w.gate.drain(timeout) | ||
| w.cancelRunning() | ||
| if finished { | ||
| return | ||
| } | ||
| slog.Warn(name+": background jobs still running after the shutdown bound, cancelling them", | ||
| slog.Int64("in_flight", w.gate.remaining()), | ||
| slog.Duration("timeout", timeout), | ||
| ) | ||
| if !w.gate.join(joinTimeout) { | ||
| slog.Warn(name+": cancelled background jobs did not return, continuing shutdown", | ||
| slog.Int64("in_flight", w.gate.remaining()), | ||
| slog.Duration("timeout", joinTimeout), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| func (w *backgroundWorkers) closeReservations() { | ||
| w.mu.Lock() | ||
| defer w.mu.Unlock() | ||
| w.closed = true | ||
| for _, cancel := range w.running { | ||
| cancel() | ||
| } | ||
| } | ||
|
|
||
| func (w *backgroundWorkers) cancelRunning() { | ||
| w.mu.Lock() | ||
| defer w.mu.Unlock() | ||
| w.cancel() | ||
| // Reservations still owned by a mutation are not jobs yet. If that | ||
| // mutation commits later, its cleanup gets a live, independently bounded | ||
| // context and remains counted in the join window. | ||
| for _, cancel := range w.running { | ||
| cancel() | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.