[ 
https://issues.apache.org/jira/browse/SOLR-18525?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Amrit Sarkar updated SOLR-18525:
--------------------------------
    Description: 
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.

  was:
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-XXXXX-repro-test.patch}}, applies on main):

{noformat}
git apply SOLR-XXXXX-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.


> 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
>         Attachments: SOLR-18525-repro-test.patch
>
>
> 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]

Reply via email to