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

Ankur Singla updated FELIX-6850:
--------------------------------
    Description: 
h2. Summary

{{ConfigurationAdminStarter.activate()}} and {{deactivate()}} mutate the shared 
{{configurationManager}} field without mutual exclusion. When they run 
concurrently on different threads, {{activate()}} can throw a 
{{{}NullPointerException{}}}, and — even without the NPE — the two methods can 
interleave so that a {{ConfigurationAdmin}} service is left orphaned (started 
but no longer tracked) or registered twice. Because so many components wait on 
Configuration Admin, this can wedge a large part of the framework.
h2. Affects Version/s

This is a latent defect, not a recent regression. The affected code was 
introduced with the required-configuration-plugin gating in FELIX-6059 and 
first shipped in {*}1.9.18{*}. It is unchanged in every release since, up to 
and including *1.9.26* and current {{master}} (1.9.27-SNAPSHOT).
h2. Symptom

Observed as a framework error during bundle refresh / startup:
{code:java}
*ERROR* ... FrameworkEvent ERROR (java.lang.NullPointerException:
Cannot invoke 
"org.apache.felix.cm.impl.ConfigurationManager.updateRegisteredConfigurationPlugins(String)"
because "this.configurationManager" is null)
{code}
h2. Root cause

{{ConfigurationAdminStarter}} exposes several lifecycle methods that all mutate 
{{configurationManager}} / {{persistenceManager}} but share no common lock:
 * Persistence-manager path: {{PersistenceManagerTracker}} calls 
{{setPersistenceManager()}} / {{unsetPersistenceManager()}} while holding its 
own {{holders}} monitor.
 * Configuration-plugin path: {{RequiredConfigurationPluginTracker}} calls 
{{updatePluginsSet()}} while holding no shared lock.
 * Bundle stop: {{Activator.stop()}} -> {{DependencyTracker.stop()}} -> 
{{{}deactivate(){}}}.

These run on independent threads, so {{activate()}} and {{deactivate()}} can 
overlap. In {{activate()}} the {{configurationManager }}field is assigned and 
then re-read on later lines:
{code:java}
configurationManager = new ConfigurationManager(pm, bundleContext);
this.startCoordinatorTracker();
final ServiceReference<ConfigurationAdmin> ref = configurationManager.start();
configurationManager.updateRegisteredConfigurationPlugins(this.registeredConfigurationPlugins);
 // NPE
{code}
If {{deactivate()}} runs on another thread between the assignment and the 
re-reads (it sets the field to {{{}null{}}}), the re-read throws the NPE 
reported above. Even when the NPE is avoided, the lack of mutual exclusion 
means an {{activate()}} that completes after a concurrent {{deactivate()}} 
leaves a started-but-untracked ConfigurationAdmin (resource/thread leak), or a 
second ConfigurationAdmin registered — an inconsistent state that never 
recovers.

A secondary, related issue: the optional Coordinator {{ServiceTracker 
}}callbacks in {{startCoordinatorTracker()}} dereference {{configurationManager 
}}directly without a null check, which can NPE on coordinator service churn 
during shutdown.
h2. When it triggers

Most easily reproduced when {{felix.cm.config.plugins}} requires one or more 
ConfigurationPlugins (so activation is gated on plugin/PM availability) and 
those services — or the Config Admin bundle itself — are refreshed/flap, e.g. 
during a large bundle-refresh cycle or product upgrade. The concurrent 
plugin/PM/stop callbacks drive {{activate()}} and {{deactivate()}} in parallel.
h2. Proposed fix

In {{{}ConfigurationAdminStarter{}}}:
 # In {{{}activate(){}}}, capture the newly created {{ConfigurationManager}} in 
a local variable and operate on that local (instead of re-reading the field), 
so an interleaving {{deactivate()}} cannot cause an NPE.
 # Serialize the lifecycle-transition methods ({{{}activate{}}}, 
{{{}deactivate{}}}, {{{}checkStart{}}}, {{{}updatePluginsSet{}}}, 
{{{}setPersistenceManager{}}}, {{{}unsetPersistenceManager{}}}, 
{{{}updateRegisteredConfigurationPlugins{}}}) so 
{{{}activate{}}}/{{{}deactivate{}}} cannot interleave and leave an orphaned or 
duplicate registration. This is consistent with the existing
PersistenceManagerTracker locking and is deadlock-safe: no path acquires the 
starter monitor and then {{holders}} (only the reverse), and the update/event 
threads and CA-registration listeners do not call back into the starter.
 # Guard the Coordinator {{ServiceTracker}} callbacks against a null 
{{{}configurationManager{}}}.

  was:
h2. Summary

{{ConfigurationAdminStarter.activate()}} and {{deactivate()}} mutate the shared
{{configurationManager}} field without mutual exclusion. When they run
concurrently on different threads, {{activate()}} can throw a
{{{}NullPointerException{}}}, and — even without the NPE — the two methods can
interleave so that a {{ConfigurationAdmin}} service is left orphaned (started 
but
no longer tracked) or registered twice. Because so many components wait on
Configuration Admin, this can wedge a large part of the framework.
h2. Affects Version/s

This is a latent defect, not a recent regression. The affected code was
introduced with the required-configuration-plugin gating in FELIX-6059 and first
shipped in {*}1.9.18{*}. It is unchanged in every release since, up to and 
including
*1.9.26* and current {{master}} (1.9.27-SNAPSHOT).
h2. Symptom

Observed as a framework error during bundle refresh / startup:
{code:java}
*ERROR* ... FrameworkEvent ERROR (java.lang.NullPointerException:
Cannot invoke 
"org.apache.felix.cm.impl.ConfigurationManager.updateRegisteredConfigurationPlugins(String)"
because "this.configurationManager" is null)
{code}
h2. Root cause

{{ConfigurationAdminStarter}} exposes several lifecycle methods that all mutate
{{configurationManager}} / {{persistenceManager}} but share no common lock:
 * Persistence-manager path: {{PersistenceManagerTracker}} calls
{{setPersistenceManager()}} / {{unsetPersistenceManager()}} while holding its
own {{holders}} monitor.
 * Configuration-plugin path: {{RequiredConfigurationPluginTracker}} calls
{{updatePluginsSet()}} while holding no shared lock.
 * Bundle stop: {{Activator.stop()}} -> {{DependencyTracker.stop()}} ->
{{{}deactivate(){}}}.

These run on independent threads, so {{activate()}} and {{deactivate()}} can
overlap. In {{activate()}} the {{configurationManager}} field is assigned and
then re-read on later lines:
{code:java}
configurationManager = new ConfigurationManager(pm, bundleContext);
this.startCoordinatorTracker();
final ServiceReference<ConfigurationAdmin> ref = configurationManager.start();
configurationManager.updateRegisteredConfigurationPlugins(this.registeredConfigurationPlugins);
 // NPE
{code}
If {{deactivate()}} runs on another thread between the assignment and the
re-reads (it sets the field to {{{}null{}}}), the re-read throws the NPE 
reported
above. Even when the NPE is avoided, the lack of mutual exclusion means an
{{activate()}} that completes after a concurrent {{deactivate()}} leaves a
started-but-untracked ConfigurationAdmin (resource/thread leak), or a second
ConfigurationAdmin registered — an inconsistent state that never recovers.

A secondary, related issue: the optional Coordinator {{ServiceTracker}}
callbacks in {{startCoordinatorTracker()}} dereference {{configurationManager}}
directly without a null check, which can NPE on coordinator service churn during
shutdown.
h2. When it triggers

Most easily reproduced when {{felix.cm.config.plugins}} requires one or more
ConfigurationPlugins (so activation is gated on plugin/PM availability) and
those services — or the Config Admin bundle itself — are refreshed/flap, e.g.
during a large bundle-refresh cycle or product upgrade. The concurrent
plugin/PM/stop callbacks drive {{activate()}} and {{deactivate()}} in parallel.
h2. Proposed fix

In {{{}ConfigurationAdminStarter{}}}:
 # In {{{}activate(){}}}, capture the newly created {{ConfigurationManager}} in 
a
local variable and operate on that local (instead of re-reading the field), so
an interleaving {{deactivate()}} cannot cause an NPE.
 # Serialize the lifecycle-transition methods ({{{}activate{}}}, 
{{{}deactivate{}}},
{{{}checkStart{}}}, {{{}updatePluginsSet{}}}, {{{}setPersistenceManager{}}},
{{{}unsetPersistenceManager{}}}, {{{}updateRegisteredConfigurationPlugins{}}}) 
so
{{{}activate{}}}/{{{}deactivate{}}} cannot interleave and leave an orphaned or
duplicate registration. This is consistent with the existing
PersistenceManagerTracker locking and is deadlock-safe: no path acquires the
starter monitor and then {{holders}} (only the reverse), and the update/event
threads and CA-registration listeners do not call back into the starter.
 # Guard the Coordinator {{ServiceTracker}} callbacks against a null
{{{}configurationManager{}}}.


> ConfigAdmin: Race condition in ConfigurationAdminStarter activate/deactivate 
> causes NPE and inconsistent Configuration Admin registration
> -----------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: FELIX-6850
>                 URL: https://issues.apache.org/jira/browse/FELIX-6850
>             Project: Felix
>          Issue Type: Bug
>          Components: Configuration Admin
>    Affects Versions: configadmin-1.9.26
>            Reporter: Ankur Singla
>            Priority: Major
>             Fix For: configadmin-1.9.28
>
>
> h2. Summary
> {{ConfigurationAdminStarter.activate()}} and {{deactivate()}} mutate the 
> shared {{configurationManager}} field without mutual exclusion. When they run 
> concurrently on different threads, {{activate()}} can throw a 
> {{{}NullPointerException{}}}, and — even without the NPE — the two methods 
> can interleave so that a {{ConfigurationAdmin}} service is left orphaned 
> (started but no longer tracked) or registered twice. Because so many 
> components wait on Configuration Admin, this can wedge a large part of the 
> framework.
> h2. Affects Version/s
> This is a latent defect, not a recent regression. The affected code was 
> introduced with the required-configuration-plugin gating in FELIX-6059 and 
> first shipped in {*}1.9.18{*}. It is unchanged in every release since, up to 
> and including *1.9.26* and current {{master}} (1.9.27-SNAPSHOT).
> h2. Symptom
> Observed as a framework error during bundle refresh / startup:
> {code:java}
> *ERROR* ... FrameworkEvent ERROR (java.lang.NullPointerException:
> Cannot invoke 
> "org.apache.felix.cm.impl.ConfigurationManager.updateRegisteredConfigurationPlugins(String)"
> because "this.configurationManager" is null)
> {code}
> h2. Root cause
> {{ConfigurationAdminStarter}} exposes several lifecycle methods that all 
> mutate {{configurationManager}} / {{persistenceManager}} but share no common 
> lock:
>  * Persistence-manager path: {{PersistenceManagerTracker}} calls 
> {{setPersistenceManager()}} / {{unsetPersistenceManager()}} while holding its 
> own {{holders}} monitor.
>  * Configuration-plugin path: {{RequiredConfigurationPluginTracker}} calls 
> {{updatePluginsSet()}} while holding no shared lock.
>  * Bundle stop: {{Activator.stop()}} -> {{DependencyTracker.stop()}} -> 
> {{{}deactivate(){}}}.
> These run on independent threads, so {{activate()}} and {{deactivate()}} can 
> overlap. In {{activate()}} the {{configurationManager }}field is assigned and 
> then re-read on later lines:
> {code:java}
> configurationManager = new ConfigurationManager(pm, bundleContext);
> this.startCoordinatorTracker();
> final ServiceReference<ConfigurationAdmin> ref = configurationManager.start();
> configurationManager.updateRegisteredConfigurationPlugins(this.registeredConfigurationPlugins);
>  // NPE
> {code}
> If {{deactivate()}} runs on another thread between the assignment and the 
> re-reads (it sets the field to {{{}null{}}}), the re-read throws the NPE 
> reported above. Even when the NPE is avoided, the lack of mutual exclusion 
> means an {{activate()}} that completes after a concurrent {{deactivate()}} 
> leaves a started-but-untracked ConfigurationAdmin (resource/thread leak), or 
> a second ConfigurationAdmin registered — an inconsistent state that never 
> recovers.
> A secondary, related issue: the optional Coordinator {{ServiceTracker 
> }}callbacks in {{startCoordinatorTracker()}} dereference 
> {{configurationManager }}directly without a null check, which can NPE on 
> coordinator service churn during shutdown.
> h2. When it triggers
> Most easily reproduced when {{felix.cm.config.plugins}} requires one or more 
> ConfigurationPlugins (so activation is gated on plugin/PM availability) and 
> those services — or the Config Admin bundle itself — are refreshed/flap, e.g. 
> during a large bundle-refresh cycle or product upgrade. The concurrent 
> plugin/PM/stop callbacks drive {{activate()}} and {{deactivate()}} in 
> parallel.
> h2. Proposed fix
> In {{{}ConfigurationAdminStarter{}}}:
>  # In {{{}activate(){}}}, capture the newly created {{ConfigurationManager}} 
> in a local variable and operate on that local (instead of re-reading the 
> field), so an interleaving {{deactivate()}} cannot cause an NPE.
>  # Serialize the lifecycle-transition methods ({{{}activate{}}}, 
> {{{}deactivate{}}}, {{{}checkStart{}}}, {{{}updatePluginsSet{}}}, 
> {{{}setPersistenceManager{}}}, {{{}unsetPersistenceManager{}}}, 
> {{{}updateRegisteredConfigurationPlugins{}}}) so 
> {{{}activate{}}}/{{{}deactivate{}}} cannot interleave and leave an orphaned 
> or duplicate registration. This is consistent with the existing
> PersistenceManagerTracker locking and is deadlock-safe: no path acquires the 
> starter monitor and then {{holders}} (only the reverse), and the update/event 
> threads and CA-registration listeners do not call back into the starter.
>  # Guard the Coordinator {{ServiceTracker}} callbacks against a null 
> {{{}configurationManager{}}}.



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

Reply via email to