Repository navigation
fix: join detached cache work before embedded storage closes - #256
perfloop[bot] wants to merge 3 commits into
Conversation
Signed-off-by: Perfloop Agent <agent@perfloop.ai>
Signed-off-by: Perfloop Agent <agent@perfloop.ai>
|
| // Use a detached context for cache writes to avoid cancellation when HTTP request completes. | ||
| // Scale timeout by content length so large objects have enough time to write. | ||
| cacheCtx, cacheCancel := context.WithTimeout(context.Background(), cacheWriteTimeoutForSize(meta.ContentLength)) | ||
| cacheCtx, cacheCancel := context.WithTimeout(s.workers.ctx, cacheWriteTimeoutForSize(meta.ContentLength)) |
There was a problem hiding this comment.
Shutdown hangs on abandoned pipe
When a full GET has finished but its whole-body populate still has queued bytes for a remote cache owner, Stop now cancels cacheCtx. ocache’s remote write can return on cancellation without draining sigReader. The relay then stays blocked in pipeWriter.Write, while the outer task waits for pipeErrCh before closing the pipe. Stop never returns even though the cache call has returned.
Close pipeReader when the cache writer exits, or drain it on error, and cover this full-GET shutdown path in a test.
There was a problem hiding this comment.
Response to this feedback.
This issue is valid. If the remote cache write returns on cancellation before consuming the pipe, the relay can remain blocked in its write while the task waits for that relay, preventing Stop from returning. I will close the pipe reader when the cache writer exits and add coverage for full-GET shutdown with queued data.
There was a problem hiding this comment.
Addressed in af7e64bf56a6f30b15c6700ed9475e85d1998b63. Response to this feedback.
The cache writer now closes its pipe reader when the cache operation returns, passing the cache error through to the relay. That releases the relay when a canceled remote write stops consuming queued data. I added a full-GET test that leaves bytes unread, cancels the Service, and verifies Stop joins without test-side draining.
Signed-off-by: Perfloop Agent <agent@perfloop.ai>
|
On SIGINT or SIGTERM, a cache-enabled TAG process now seals detached-work admission, cancels the Service lifetime, and joins admitted cache users after HTTP handlers drain but before
The same process, admission, and reservation assertion is violated on test-only comparison The repository checks No production shutdown incidence or overall shutdown duration was measured. HTTP draining keeps its existing 30-second deadline; the worker join has no deadline and an uncooperative cache operation can outlast the bundled StatefulSet's 30-second grace. No fixed total completion time is promised. This ordering applies after successful HTTP draining, not to handlers that survive the deadline. See graceful shutdown. Generated by Perfloop. |
proxy.Service.Stopnow seals detached-work admission, cancels the Service lifetime, and joins admitted cache users after HTTP handlers drain but beforemaincloses embedded storage. The Service lifetime is independent of an individual request, so a cache warm can continue after a client disconnect; shutdown cancels and joins it. Full-object writes have their own registered owner, so a fetch-supervisor timeout cannot hide a still-runningPutStreamor release its reservation early. An admitted tiered cleanup retains its timeout through the conditional S3 delete and marker repair. Shutdown still joins it because cancellation after dispatch can leave the remote result ambiguous.TestShutdownWaitsForBackgroundWriterBeforeEmbeddedCloseruns TAG as a child process with embedded storage and a controlled local S3 origin. It holds an admitted writer through SIGTERM and records admission, cancellation, writer exit, and backend close. A retained result-mapping driver also runs the cold-range process after the fetch supervisor times out and the active-fetch count returns to zero, plus a separate active-writer control.TestShutdownDrainsNormalRequestWithinGracechecks an ordinary request completing within the existing HTTP grace.TestStopWaitsForWriterAfterFetchSupervisorTimesOutobserves the reservation until the writer exits, andTestTieredCleanupFinishesMarkerRepairDuringServiceStopcovers the joined conditional-delete path. The existingTestColdOwner_RangeWarmsCacheOnClientCancelintegration test remains in the suite.The same process-and-reservation assertion is violated on test-only comparison
84ae52583b59e4299e6bf23bb354c59be853f5c3and satisfied on the final source.make build,make test,make test-integration,go test -race -count=1 ./proxy, andmake lint-cipassed. The process and result-mapping drivers are retained verification support, not part of this PR.No production shutdown incidence or overall shutdown duration was measured. The HTTP drain keeps its 30-second deadline; the worker join has none, and an uncooperative cache operation can outlast the bundled StatefulSet's 30-second termination grace. No fixed total completion time is promised. This ordering applies after successful HTTP draining, not to handlers that survive its deadline. See graceful shutdown.
Generated by Perfloop.
Note
Medium Risk
Changes shutdown ordering and how long-running cache goroutines are canceled and joined; incorrect behavior could prolong shutdown or close RocksDB while writers still run, but behavior is heavily tested and scoped to lifecycle boundaries.
Overview
Graceful shutdown now drains HTTP first, then calls
proxy.Service.Stop()to cancel service-scoped work and block until admitted background cache tasks finish—before embedded storage closes inmain.A new
serviceWorkerslifecycle (startBackgroundTask/beginBackgroundTask) replaces ad-hoc goroutines across populate, block fetch, parquet prefetch, tiered cleanup, and upstream streaming. Detached work uses a service lifetime context (not only the request), so shutdown can cancel and join it. Populate and block-fetch ownership is tightened: cache slots and byte budget stay held until the realPutStream/PutBlockexits; abandoned pipe readers are closed when writers return; full-object background writes register a separate supervised task so supervisor timeout cannot release reservations early. Tiered cleanup keeps its own timeout through conditional DELETE and marker repair while still being joined on stop.Docs add graceful-shutdown notes in README and
docs/deploy.md. Tests include subprocess shutdown ordering probes, integrationService.Stop()in teardown, and unit tests for stop vs. held writers and tiered repair during stop.Reviewed by Cursor Bugbot for commit af7e64b. Bugbot is set up for automated code reviews on this repo. Configure here.