[13.x] Fix Pipeline memory retention in long-lived workers#59415
Closed
JoshSalway wants to merge 1 commit intolaravel:13.xfrom
Closed
[13.x] Fix Pipeline memory retention in long-lived workers#59415JoshSalway wants to merge 1 commit intolaravel:13.xfrom
JoshSalway wants to merge 1 commit intolaravel:13.xfrom
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for submitting a PR! Note that draft PRs are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface. Pull requests that are abandoned in draft may be closed due to inactivity. |
This comment was marked as spam.
This comment was marked as spam.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Fixes 🟢 #56395. Related to 🔴 #16783, 🔴 #25999.
Pipeline retains references to
$passableand$pipesafterthen()completes. In long-lived processes (Octane, queue workers,dispatchSync/dispatchNow), this prevents garbage collection of potentially large objects until the Pipeline instance itself is collected.Changes
Adds cleanup in the
finallyblock ofPipeline::then():This runs after the finally callback (which still receives
$passable), so existing behavior is preserved. No breaking changes —passableandpipesare only used during pipeline execution. Afterthen()returns, they serve no purpose.Benchmarks (PHP 8.5.1)
Tested with common job types. Pipeline instance is kept alive after each job completes (simulating Octane/queue workers). Measured with
memory_get_usage(true).Without fix:
With fix:
Simulated sequential heavy jobs (single reused Pipeline, like a queue worker):
CSV import (100K rows, ~80 MiB), PDF report (50 pages, ~24 MiB), CSV import (80K rows, ~60 MiB).
Without fix:
With fix:
Impact
Kerneland route middleware inRouter). Without cleanup, the request object and all middleware instances are retained until the next request overwrites them. With cleanup, they're eligible for GC immediately after the response is sent. This matters most in long-lived processes like Octane where the process persists between requests.dispatchSync/dispatchNowroute through a single Pipeline instance stored in theDispatcherconstructor — it's reused across every dispatch. Without cleanup, the previous command's data is retained until the next dispatch overwrites it.CallQueuedHandlercreates a new Pipeline per job, then dispatches through the Bus Dispatcher's reused Pipeline viadispatchNow(). Without cleanup, ghost memory from previous jobs can push workers overmemory_limit.passableandpipesare only used during pipeline execution. Afterthen()returns, they serve no purpose.Test plan
6 tests added to
PipelineTest.php:testPipelineClearsReferencesAfterThen— passable and pipes are null/empty after then()testPipelineClearsReferencesAfterThenReturn— same for thenReturn()testPipelineClearsReferencesAfterException— cleanup happens even when pipeline throwstestPipelineFinallyReceivesPassableBeforeCleanup— finally callback gets passable before it's nulledtestPipelineAllowsGarbageCollectionAfterThen— WeakReference confirms object is GC'dtestPipelineWorksCorrectlyWhenReused— pipeline can be reused after cleanupCompanion fix: #59329 handles the other side — when OOM does happen, an optimistic exception counter ensures the job fails gracefully instead of retrying forever.