You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ExecutionReconfigurationService.registerWorkerCompletionCallback discarded the Disposable returned by client.registerCallback, so its engine callback outlived the service. It now hands it to addSubscription(...), matching registerCompletionDiffHandler directly below it.
It was the only registerCallback site in org.apache.texera.web.service not doing this:
File
sites
wrapped
ExecutionStatsService.scala
5
yes
ExecutionResultService.scala
3
yes
ExecutionConsoleService.scala
2
yes
ExecutionRuntimeService.scala
1
yes
ExecutionReconfigurationService.scala
1
no
Why this actually leaks, rather than being tidied up by something else
The teardown path is live: WorkflowExecutionService.unsubscribeAll (WorkflowExecutionService.scala:185) calls executionReconfigurationService.unsubscribeAll(), which released every other subscription but not this one.
And client.shutdown() is not an alternative release — it flips isActive and poison-pills the ClientActor, but never touches registeredObservables, which is written only in registerCallback and never cleared. The PublishSubject chain therefore keeps the subscriber closure, and through it the service, its ExecutionStateStore and its Workflow, for as long as the AmberClient is reachable — which WorkflowExecutionService.client guarantees.
The fix is pinned
New test: "the worker completion callback" should "release the engine subscription once the service is unsubscribed". Verified with the production file reverted and restored:
production reverted
with fix
ExecutionReconfigurationServiceSpec
13 passed, 1 failed
14 passed
The before-state failure is the right one — ArrayBuffer() did not contain element class …UpdateExecutorCompleted, i.e. the disposable was never disposed because it was never registered.
Two additions to the existing TestAmberClient double were needed, and the reasons are worth stating: an explicit disposedCallbacks record, because disposal-removal from the callbacks map alone is indistinguishable from "never registered" or "cleared by reset"; and a fireIfRegistered helper, because the strict fire calls fail(...) when nothing is registered and so cannot express "a late event is inert".
Two comments were rewritten, not just tests added
Both would otherwise have contradicted the code:
TestAmberClient.registerCallback's scaladoc said production "currently DISCARDS it" and that "no test below fires an engine event after unsubscribeAll, so the suite neither depends on the leak nor breaks when it is fixed." Both clauses are now false.
The routing comment in "stop announcing completions once the service is unsubscribed" called the engine callback an "unrelated, currently broken, seam". The routing decision — drive that test through onWorkerReconfigured rather than the engine event — is still right and unchanged, but re-justified: the two are separate subscriptions, so routing through the engine event would leave an empty batch explainable by either one being released. That test's assertion is untouched, so it stays agnostic exactly as test(amber): cover the reconfiguration service past its own test seams #7692 intended.
Verification
ExecutionReconfigurationServiceSpec 14/14.
Dependents and neighbours: ExecutionRuntimeServiceSpec, WorkflowExecutionServiceSpec, ExecutionStatsServiceSpec, ExecutionConsoleServiceSpec, WorkflowWebsocketResourceSpec, TexeraWebSocketEventSpec — 59/59 across 6 suites, 0 aborted.
scalafmtCheck, Test/scalafmtCheck, scalafixAll --check all pass. (The one scalafix warning is a pre-existing // scalafix:ok in OutputManagerSpec, unrelated.)
One thing deliberately left alone
The suite still carries an assertion recorded as observed, not endorsed: an N-worker operator fires N ModifyLogicCompletedEvents, which contradicts registerCompletionDiffHandler's own comment claiming the frontend is notified once all workers finish. That is a separate defect with its own decision to make, so this PR does not touch it.
This fix: PR was checked against each actively-supported release branch. release/* labels drive the post-merge backport, so add or remove one to change where this fix lands.
Release branch
Analysis
✅ release/v1.2
Change detected on this branch — label added; this fix is queued to backport here. Requested review from @xuang7.
Compared against main 2edbdf9 benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.
✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.50%. Comparing base (2edbdf9) to head (73dfb45).
✅ All tests successful. No failed tests found.
📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
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
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.
What changes were proposed in this PR?
ExecutionReconfigurationService.registerWorkerCompletionCallbackdiscarded theDisposablereturned byclient.registerCallback, so its engine callback outlived the service. It now hands it toaddSubscription(...), matchingregisterCompletionDiffHandlerdirectly below it.It was the only
registerCallbacksite inorg.apache.texera.web.servicenot doing this:ExecutionStatsService.scalaExecutionResultService.scalaExecutionConsoleService.scalaExecutionRuntimeService.scalaExecutionReconfigurationService.scalaWhy this actually leaks, rather than being tidied up by something else
The teardown path is live:
WorkflowExecutionService.unsubscribeAll(WorkflowExecutionService.scala:185) callsexecutionReconfigurationService.unsubscribeAll(), which released every other subscription but not this one.And
client.shutdown()is not an alternative release — it flipsisActiveand poison-pills theClientActor, but never touchesregisteredObservables, which is written only inregisterCallbackand never cleared. ThePublishSubjectchain therefore keeps the subscriber closure, and through it the service, itsExecutionStateStoreand itsWorkflow, for as long as theAmberClientis reachable — whichWorkflowExecutionService.clientguarantees.The fix is pinned
New test:
"the worker completion callback" should "release the engine subscription once the service is unsubscribed". Verified with the production file reverted and restored:ExecutionReconfigurationServiceSpecThe before-state failure is the right one —
ArrayBuffer() did not contain element class …UpdateExecutorCompleted, i.e. the disposable was never disposed because it was never registered.Two additions to the existing
TestAmberClientdouble were needed, and the reasons are worth stating: an explicitdisposedCallbacksrecord, because disposal-removal from thecallbacksmap alone is indistinguishable from "never registered" or "cleared byreset"; and afireIfRegisteredhelper, because the strictfirecallsfail(...)when nothing is registered and so cannot express "a late event is inert".Two comments were rewritten, not just tests added
Both would otherwise have contradicted the code:
TestAmberClient.registerCallback's scaladoc said production "currently DISCARDS it" and that "no test below fires an engine event afterunsubscribeAll, so the suite neither depends on the leak nor breaks when it is fixed." Both clauses are now false."stop announcing completions once the service is unsubscribed"called the engine callback an "unrelated, currently broken, seam". The routing decision — drive that test throughonWorkerReconfiguredrather than the engine event — is still right and unchanged, but re-justified: the two are separate subscriptions, so routing through the engine event would leave an empty batch explainable by either one being released. That test's assertion is untouched, so it stays agnostic exactly as test(amber): cover the reconfiguration service past its own test seams #7692 intended.Verification
ExecutionReconfigurationServiceSpec14/14.ExecutionRuntimeServiceSpec,WorkflowExecutionServiceSpec,ExecutionStatsServiceSpec,ExecutionConsoleServiceSpec,WorkflowWebsocketResourceSpec,TexeraWebSocketEventSpec— 59/59 across 6 suites, 0 aborted.scalafmtCheck,Test/scalafmtCheck,scalafixAll --checkall pass. (The one scalafix warning is a pre-existing// scalafix:okinOutputManagerSpec, unrelated.)One thing deliberately left alone
The suite still carries an assertion recorded as observed, not endorsed: an N-worker operator fires N
ModifyLogicCompletedEvents, which contradictsregisterCompletionDiffHandler's own comment claiming the frontend is notified once all workers finish. That is a separate defect with its own decision to make, so this PR does not touch it.Any related issues, documentation, discussions?
Closes #7822
How was this PR tested?
Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 5)