[ 
https://issues.apache.org/jira/browse/SLING-13259?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18094332#comment-18094332
 ] 

Joerg Hoh commented on SLING-13259:
-----------------------------------

That feels a bit incomplete to me, because if just one PropertyProvider is 
supposed to be stopped (the Sling instance itself is not going to be shut 
down), this approach won't help.

I wonder why on unbind of a PropertyProvider {{doUpdateProperties()}} is called 
at all. Or if we can do the call to {{doUpdateProperties()}} in an async way 
and moving it off the critical thread.

 

> OakDiscoveryService can block framework shutdown when Oak CommitQueue is slow 
> during PropertyProvider unbind 
> -------------------------------------------------------------------------------------------------------------
>
>                 Key: SLING-13259
>                 URL: https://issues.apache.org/jira/browse/SLING-13259
>             Project: Sling
>          Issue Type: Bug
>          Components: Discovery
>            Reporter: Daniel Iancu
>            Priority: Major
>
> h2. Description
> h3. What happens
> During OSGi framework shutdown, the Felix start-level thread can block for 
> many minutes — potentially indefinitely — inside 
> {{{}OakDiscoveryService.doUpdateProperties(){}}}, waiting for a JCR commit 
> that never completes. The stuck thread prevents the framework from stopping 
> any further bundles, so the JVM only exits when forcibly killed by the 
> surrounding process manager. During that window the instance is half-dead but 
> still visible to the rest of the cluster, delaying topology convergence on 
> the surviving members.
> h3. Stack trace
> {noformat}
>  FelixStartLevel
>         at jdk.internal.misc.Unsafe.park([email protected]/Native Method)
>         - parking to wait for  <0x00000007d79a3e78> (a 
> java.util.concurrent.CountDownLatch$Sync)
>         at 
> java.util.concurrent.locks.LockSupport.park([email protected]/LockSupport.java:221)
>         at 
> java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire([email protected]/AbstractQueuedSynchronizer.java:754)
>         at 
> java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly([email protected]/AbstractQueuedSynchronizer.java:1099)
>         at 
> java.util.concurrent.CountDownLatch.await([email protected]/CountDownLatch.java:230)
>         at 
> org.apache.jackrabbit.oak.plugins.document.CommitQueue$Entry.await(CommitQueue.java:346)
>         at 
> org.apache.jackrabbit.oak.plugins.document.CommitQueue.waitUntilHeadOfQueue(CommitQueue.java:285)}
> ...
>         at 
> com.adobe.granite.repository.impl.CRX3SessionImpl.save(CRX3SessionImpl.java:232)
>         at 
> org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.commit(JcrResourceProvider.java:617)
>         at 
> org.apache.sling.resourceresolver.impl.providers.stateful.AuthenticatedResourceProvider.commit(AuthenticatedResourceProvider.java:268)
>         at 
> org.apache.sling.resourceresolver.impl.helper.ResourceResolverControl.commit(ResourceResolverControl.java:565)
>         at 
> org.apache.sling.resourceresolver.impl.ResourceResolverImpl.commit(ResourceResolverImpl.java:1056)
>         at 
> org.apache.sling.discovery.oak.OakDiscoveryService.doUpdateProperties(OakDiscoveryService.java:550)
>         at 
> org.apache.sling.discovery.oak.OakDiscoveryService.bindPropertyProviderInteral(OakDiscoveryService.java:416)
>         at 
> org.apache.sling.discovery.oak.OakDiscoveryService.updatedPropertyProvider(OakDiscoveryService.java:430)
>         - locked <0x00000006afc353a0> (a java.lang.Object)
> ... {noformat}
> h3. Root cause
> {{OakDiscoveryService}} declares {{PropertyProvider}} as a {{{}MULTIPLE{}}}, 
> {{DYNAMIC}} reference. Every bind / unbind / update callback triggers a JCR 
> write via {{doUpdateProperties()}} → {{ResourceResolver.commit()}} in order 
> to keep the properties node at {{//properties}} in sync with the currently 
> registered providers.
> At shutdown, application bundles (higher start levels) typically stop before 
> infrastructure bundles like {{discovery.oak}} (low start level). When an 
> application bundle stops, its {{PropertyProvider}} services are unregistered, 
> and Declarative Services invokes {{unbindPropertyProvider}} / 
> {{updatedPropertyProvider}} on {{OakDiscoveryService}} synchronously on the 
> Felix start-level thread. That callback then issues an Oak commit.
> If the Oak {{CommitQueue}} is slow at that moment — a pending entry ahead of 
> us, repository under load, backlog on the cluster leader — the commit parks 
> on a {{CountDownLatch}} and does not return. The Felix start-level thread is 
> now stuck inside a callback for a bundle it hasn't finished stopping. It 
> cannot advance to stop any further bundles, and in particular it cannot reach 
> {{{}discovery.oak{}}}'s own bundle to trigger 
> {{{}OakDiscoveryService.deactivate(){}}}. Shutdown stalls entirely.
> h3. Why the existing {{activated}} guard doesn't help
> {{OakDiscoveryService.activated}} is set to {{false}} inside 
> {{{}deactivate(){}}}, several lines into the method, under 
> {{{}viewStateManagerLock{}}}. In the scenario above {{deactivate()}} is never 
> reached, so {{activated}} remains {{true}} and every unbind callback proceeds 
> into {{doUpdateProperties()}} and into {{{}commit(){}}}.
> Even in cases where {{deactivate()}} does run, the {{activated}} flag is 
> written under a different lock ({{{}viewStateManagerLock{}}}) from the one 
> guarding bind/unbind ({{{}lock{}}}), so the two are not mutually observable 
> without racing.
> h3. Why the property update is unnecessary at shutdown
> Once this instance is shutting down, rewriting the properties node has no 
> value — no other cluster member will meaningfully act on the updated contents 
> before this instance is removed from the topology anyway. The write is pure 
> overhead, and during shutdown it is actively harmful because it holds the 
> framework thread.
> h2. Impact
> Framework shutdown can hang for the full duration of the surrounding process 
> manager's grace period before the JVM is forcibly killed.
> The stalled instance remains visible in the discovery topology throughout, 
> delaying leader re-election and topology convergence for the rest of the 
> cluster.
> Any operational tooling that assumes clean JVM exit (health checks, rolling 
> restarts, orchestrated deployments) is affected.
> h2. Steps to reproduce
> Start a Sling instance with {{discovery.oak}} active and at least one 
> {{PropertyProvider}} implementation registered by a bundle at a higher start 
> level than {{{}discovery.oak{}}}.
> Induce Oak {{CommitQueue}} slowness — for example by holding an open write 
> session on another thread, or by loading the repository with concurrent 
> writers that outpace the commit rate.
> Trigger a clean framework shutdown (e.g. {{bin/stop}} or SIGTERM).
> Observe: shutdown does not complete. A thread dump shows {{FelixStartLevel}} 
> parked in {{CommitQueue$Entry.await}} under {{{}doUpdateProperties{}}}. The 
> JVM only exits on forcible termination.
> h2. Expected behaviour
> Once the framework has begun shutting down, {{OakDiscoveryService}} should 
> skip all property-update writes and return immediately from 
> {{{}doUpdateProperties(){}}}. Framework shutdown should proceed at its normal 
> pace regardless of the state of the Oak {{{}CommitQueue{}}}.
> h2. Proposed fix (summary)
> Introduce a {{volatile boolean deactivating}} flag that short-circuits 
> {{doUpdateProperties()}} once shutdown has begun. The flag must be set before 
> the first blocking commit can start, which means it cannot rely solely on 
> {{@Deactivate}} being invoked — because in the observed scenario 
> {{deactivate()}} is never reached. The trigger should be one of:
> A {{FrameworkListener}} on {{{}FrameworkEvent.STOPPING{}}}, registered in 
> {{{}@Activate{}}}, that sets the flag as soon as the framework begins 
> shutting down. or
> An inline check of {{bundleContext.getBundle(0).getState() >= 
> Bundle.STOPPING}} at the top of {{{}doUpdateProperties(){}}}.
> The flag is also set at the first line of {{deactivate()}} as a 
> belt-and-braces measure for the case where {{discovery.oak}} is deactivated 
> independently of a full framework stop.
> The fix must not interrupt an already-in-progress commit — that risks 
> corrupting the Oak session. It only prevents new commits from starting once 
> shutdown has begun.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to