[
https://issues.apache.org/jira/browse/SOLR-18525?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
ASF GitHub Bot updated SOLR-18525:
----------------------------------
Labels: pull-request-available (was: )
> Core create hangs forever if its replica is removed from cluster state during
> registration
> ------------------------------------------------------------------------------------------
>
> Key: SOLR-18525
> URL: https://issues.apache.org/jira/browse/SOLR-18525
> Project: Solr
> Issue Type: Bug
> Components: SolrCloud
> Affects Versions: 9.9, 9.10, 10.0, 9.11, 9.10.1
> Reporter: Amrit Sarkar
> Priority: Major
> Labels: pull-request-available
> Attachments: SOLR-18525-repro-test.patch
>
> Time Spent: 10m
> Remaining Estimate: 0h
>
> If a replica is removed from state.json while the node hosting it is still
> inside {{CoreContainer.create}} for that core, the create never returns and
> the core name stays locked on that node.
> h3. When it happens
> {{ZkController.register}} checks that the replica exists in cluster state,
> joins leader election and waits for a leader (up to leaderVoteWait + 10 min),
> replays any tlog, and only then installs {{UnloadCoreOnDeletedWatcher}}. If
> the replica is removed in that window, the watcher sees it gone and calls
> {{CoreContainer.unload}} on a core that is still being created.
> Since SOLR-17700, {{unload}} waits on the per-core pending op. The create
> already holds that op, so:
> # *Node already hosts a replica of the collection:* the create thread runs
> the watcher inline from {{registerDocCollectionWatcher}} and waits on itself.
> # *First replica of the collection on the node:* registering the watcher also
> queues an async notification. The notification thread takes the watcher
> monitor ({{onStateChanged}} is synchronized) and waits for the pending op.
> The create thread holds the op and waits for the monitor.
> The JVM doesn't flag either as a deadlock, because the per-core lock is the
> {{pendingCoreOps}} set, not a monitor. The node just looks stuck.
> It takes a slow register to hit this with the Collection API. I reproduced it
> with a plain DELETEREPLICA on default timeouts: DELETEREPLICA waits for the
> shard lock until ADDREPLICA gives up (90s), its core UNLOAD then parks behind
> the create's pending op until the request times out (90s more), and after
> another 30s it removes the replica from state.json. If register is still
> waiting for a leader at that point (around 210s in), the create deadlocks.
> That wait can last leaderVoteWait + 10 min when the shard has no live leader.
> A failed ADDREPLICA followed by a DELETEREPLICA cleanup is a likely way to
> get there. I haven't tested split rollback (DELETESHARD).
> Startup load releases the pending op before registering in the background, so
> only {{CoreContainer.create}} is affected (ADDREPLICA, collection create,
> split sub-shards). That's why
> {{DeleteReplicaTest.raceConditionOnDeleteAndRegisterReplica}}, which covers
> the restart path, doesn't catch it.
> h3. Repro
> I found this while going through the core create and unload locking, and
> reproduced it locally with a test that forces the race. The test is attached
> ({{SOLR-18525-repro-test.patch}}, applies on main):
> {noformat}
> git apply SOLR-18525-repro-test.patch
> ./gradlew :solr:core:test --tests
> "org.apache.solr.cloud.CoreCreateReplicaRemovedDeadlockTest"
> {noformat}
> It stretches the window with a test {{UpdateLog}} that blocks in
> {{recoverFromLog()}}, the last hook point after the existence check, removes
> the replica while register is paused, then lets the create continue. Five
> tests, all fail every time, with and without the Overseer (seeds
> BAE60C1D1A3FBB96 and 814077321E33EF2E):
> * both variants with the replica removed by a DELETECORE state update (fast,
> about 45s each)
> * both variants with the replica removed by a real DELETEREPLICA on default
> timeouts (about 4 min each)
> * follow-up core admin calls on the stuck core name
> On timeout each test fails with the stacks of the stuck threads.
> h3. Stack (variant 1, main @ 22a8cfebbbd)
> {noformat}
> "qtp1724204461-60-null-8" WAITING on java.lang.Object@142135dd
> (SolrCores.modifyLock)
> at o.a.s.core.SolrCores.waitAddPendingCoreOps(SolrCores.java:291)
> at o.a.s.core.CoreContainer.unload(CoreContainer.java:2064)
> at
> o.a.s.cloud.ZkController$UnloadCoreOnDeletedWatcher.onStateChanged(ZkController.java:2970)
> - locked o.a.s.cloud.ZkController$UnloadCoreOnDeletedWatcher@439e24f4
> at
> o.a.s.common.cloud.ZkStateReader.registerDocCollectionWatcher(ZkStateReader.java:1626)
> at o.a.s.cloud.ZkController.register(ZkController.java:1572)
> at o.a.s.cloud.ZkController.register(ZkController.java:1385)
> at o.a.s.core.ZkContainer.lambda$registerInZk$0(ZkContainer.java:261)
> at o.a.s.core.ZkContainer.registerInZk(ZkContainer.java:290)
> at o.a.s.core.CoreContainer.registerCore(CoreContainer.java:1465)
> at o.a.s.core.CoreContainer.createFromDescriptor(CoreContainer.java:1692)
> at o.a.s.core.CoreContainer.create(CoreContainer.java:1548)
> {noformat}
> Variant 2:
> {noformat}
> "qtp1724204461-52-null-4" BLOCKED on
> ZkController$UnloadCoreOnDeletedWatcher@26c37a27 owned by
> "watches-44-thread-1-..."
> at
> o.a.s.cloud.ZkController$UnloadCoreOnDeletedWatcher.onStateChanged(ZkController.java:2964)
> at
> o.a.s.common.cloud.ZkStateReader.registerDocCollectionWatcher(ZkStateReader.java:1626)
> at o.a.s.cloud.ZkController.register(ZkController.java:1572)
> ... down to CoreContainer.create(CoreContainer.java:1548)
> "watches-44-thread-1-..." WAITING on java.lang.Object@142135dd
> at o.a.s.core.SolrCores.waitAddPendingCoreOps(SolrCores.java:291)
> at o.a.s.core.CoreContainer.unload(CoreContainer.java:2064)
> at
> o.a.s.cloud.ZkController$UnloadCoreOnDeletedWatcher.onStateChanged(ZkController.java:2970)
> - locked ZkController$UnloadCoreOnDeletedWatcher@26c37a27
> at
> o.a.s.common.cloud.ZkStateReader$Notification.run(ZkStateReader.java:1950)
> {noformat}
> h3. Impact
> * The core admin CREATE never returns. ADDREPLICA fails after the 90s shard
> request timeout.
> * The core stays loaded, and its state updates are rejected ("Failed to
> update state because the replica does not exist").
> * Every later state.json change for the collection starts another watcher
> thread that blocks on the same monitor.
> * On that node, core admin UNLOAD and RELOAD of the same core name block (no
> response within the test's 20s bound, threads parked in
> {{waitAddPendingCoreOps}}), and CREATE with the same name fails with "Already
> creating a core with name ...". DELETEREPLICA's own UNLOAD thread stays
> parked after its request times out.
> * Stopping the node releases everything (shutdown completes in about half a
> second). I found no other way to clear it.
> h3. Where it came from
> SOLR-17700 (fix version 9.9) made the public {{unload}} take the core op lock
> and moved the old body to {{unloadWithoutCoreOp}}. It switched the
> {{NotInClusterStateException}} path inside {{createFromDescriptor}} to
> {{unloadWithoutCoreOp}}, but the watcher path through {{register}} still
> calls {{unload}}. 9.8.1's {{unload}} doesn't take the lock, so it doesn't
> hang here. Reverting SOLR-17700 isn't the answer though: before it, the
> watcher would likely unload the core in the middle of the create.
> Related: SOLR-12187 (added the watcher), SOLR-16497 (finer grained locking in
> SolrCores).
> I'm working on a fix.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]