Daniel Iancu created SLING-13259:
------------------------------------
Summary: 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
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 (Native Method) -
parking to wait for <0x…> (a java.util.concurrent.CountDownLatch
Entry.await (CommitQueue.java:346) at
org.apache.jackrabbit.oak.plugins.document.CommitQueue.waitUntilHeadOfQueue
(CommitQueue.java:285) … at
org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.commit at
org.apache.sling.resourceresolver.impl.ResourceResolverImpl.commit at
org.apache.sling.discovery.oak.OakDiscoveryService.doUpdateProperties
(OakDiscoveryService.java:550) at
org.apache.sling.discovery.oak.OakDiscoveryService.bindPropertyProviderInteral
at org.apache.sling.discovery.oak.OakDiscoveryService.updatedPropertyProvider
{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)