Repository navigation
[fix][broker] Fix TableViewLoadDataStoreImpl close deadlock that stalls broker shutdown - #26243
Merged
Merged
Conversation
…ls broker shutdown Fixes apache#24628 TableViewLoadDataStoreImpl closed its table view and producer with blocking close() calls while holding the store monitor (all methods are synchronized). The table view reader's close future can only complete via a task on the Pulsar client's internal pinned executor thread (ConsumerImpl.cleanupAtClose -> failPendingReceive). When that same thread is concurrently handling a ServiceUnitStateChannel event whose StateChangeListener calls into the store (BrokerLoadDataReporter tombstone -> removeAsync, also synchronized), the two threads deadlock: the closing thread holds the monitor and waits for the close future, while the executor thread that must complete it waits for the monitor. In CI this permanently hung PulsarService.close() inside ExtensibleLoadManagerImpl.disableBroker -> stopLoadDataReportTasks, making ExtensibleLoadManagerCloseTest.testCloseAfterLoadingBundles time out after 300s and poisoning subsequent invocations (deferGetOwner timeouts from half-closed zombie brokers). Close the producer and table view with non-blocking closeAsync() and log failures instead, so no client close is awaited while holding the store monitor. PulsarService closes the shared internal client later in shutdown, so in-flight closes cannot leak. Add a deterministic regression test that reproduces the interleaving with a stubbed table view whose close future only completes after another thread enters a synchronized store method; it deadlocks on the old code and passes with the fix. Assisted-by: Claude Code (claude-fable-5)
lhotari
requested review from
Technoboy-,
dao-jun,
heesung-sohn,
merlimat and
nodece
July 25, 2026 13:30
heesung-sohn
approved these changes
Jul 25, 2026
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…ls broker shutdown (apache#26243) (cherry picked from commit aa3ec21)
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…ls broker shutdown (apache#26243) (cherry picked from commit aa3ec21)
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…ls broker shutdown (apache#26243) (cherry picked from commit aa3ec21)
nodece
pushed a commit
to ascentstream/pulsar
that referenced
this pull request
Aug 28, 2026
…ls broker shutdown (apache#26243) (cherry picked from commit aa3ec21)
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.
Fixes #24628
Motivation
ExtensibleLoadManagerCloseTest.testCloseAfterLoadingBundleshas been flaky in CI for a long time (#24628), with several failure signatures. Analysis of a recent failure (test-report artifact of this run) shows the reporteddeferGetOwnerTimeoutExceptionis only fallout: one invocation earlier,broker.close()hung for 300 seconds until the TestNG method timeout killed the test thread, leaving half-closed zombie brokers that poisoned the subsequent invocation's lookups.The hang is a deadlock in
TableViewLoadDataStoreImplthat can stall broker shutdown in production:TableViewLoadDataStoreImplaresynchronized, andshutdown()/close()performed a blockingtableView.close()while holding the store monitor (viaExtensibleLoadManagerImpl.disableBroker→stopLoadDataReportTasks).ConsumerImpl.cleanupAtClose→failPendingReceive).ServiceUnitStateChannelStateChangeListenercalling back into the store —BrokerLoadDataReporter.handleEvent→tombstone()→removeAsync(), alsosynchronized— where it blocks on the store monitor.The closing thread holds the monitor and waits for the close future; the only thread that can complete that future waits for the monitor: a permanent deadlock. The trigger window is exactly what the test creates: a broker closing while
Ownedevents from a previously closed broker's bundle handoff are still being processed. The captured logs confirm the mechanism: the reader'sClosed consumerwire-level log appears immediately, and the blocked executor thread'sRestarted producer ... restartReason=object is nullfires at the exact moment the 300s interrupt releases the monitor.The same lock/executor contention also explains the other #24628 signatures (bounded 5s
startTableView/startProducerrestart failures and cascade failures in later invocations).Modifications
TableViewLoadDataStoreImpl.closeTableView()/closeProducer(): close the table view and producer with non-blockingcloseAsync()and log failures, instead of blockingclose()under the store monitor. No client close is awaited while holding the monitor anymore, which removes the deadlock.PulsarServicecloses the shared internal client later during shutdown, so in-flight asynchronous closes cannot leak.LoadDataStoreTest.testShutdownDoesNotDeadlockWithConcurrentStoreAccessthat reproduces the interleaving with a stubbed table view whose close future only completes after another thread has entered a synchronized store method. It deadlocks (30s test timeout) on the previous code and passes with the fix.@VisibleForTestingsetTableViewaccessor used by the test.Verifying this change
This change added tests and can be verified as follows:
LoadDataStoreTest.testShutdownDoesNotDeadlockWithConcurrentStoreAccessdeterministically reproduces the deadlock: it fails with a 30s timeout on the previous code (test thread parked in the blocking close holding the store monitor) and passes with the fix.LoadDataStoreTestpasses (7/7).ExtensibleLoadManagerCloseTestpasses locally with all 10invocationCountruns oftestCloseAfterLoadingBundlesplustestLookup.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes