This is an automated email from the ASF dual-hosted git repository.

tjwatson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 026b877c97 FELIX-6633 : Dynamic references may miss events while 
causing deactivation
     new d84e21bacb Merge pull request #219 from tjwatson/FELIX-6633
026b877c97 is described below

commit 026b877c97b3874dd32cd8a820c0b22574a84a08
Author: Thomas Watson <[email protected]>
AuthorDate: Mon Aug 21 07:58:17 2023 -0500

    FELIX-6633 : Dynamic references may miss events while causing deactivation
    
    Ensure no new add events got missed when deactivating on removed service
    
    This change follows the same pattern as other reference types but
    also does it for dynamic references when they become unsatisifed.
    
    The fix is to simply cause the dependency manager to be checked
    again after the removed event gets processed to ensure the dependency
    is not now satisified.
---
 .../felix/scr/impl/manager/DependencyManager.java  |  10 ++
 .../scr/integration/ConfigurationChangeTest.java   | 132 ++++++++++++++++++++-
 ...test_simple_components_configuration_change.xml |   6 +-
 3 files changed, 146 insertions(+), 2 deletions(-)

diff --git 
a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java 
b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
index 813c8230d3..94e8f14575 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
@@ -412,6 +412,11 @@ public class DependencyManager<S, T> implements 
ReferenceManager<S, T>
                 m_componentManager.getLogger().log(Level.DEBUG,
                     "dm {0} tracking {1} MultipleDynamic removed (deactivate) 
{2}",
                     null, getName(), trackingCount, serviceReference );
+                if (event != null) {
+                    // After event has been processed make sure to check if we 
missed anything
+                    // while deactivating; this will possibly reactivate
+                    event.addComponentManager(m_componentManager);
+                }
             }
             ungetService(refPair);
         }
@@ -1136,6 +1141,11 @@ public class DependencyManager<S, T> implements 
ReferenceManager<S, T>
                 tracked(trackingCount);
                 untracked = false;
                 deactivateComponentManager();
+                if (event != null) {
+                    // After event has been processed make sure to check if we 
missed anything
+                    // while deactivating; this will possibly reactivate
+                    event.addComponentManager(m_componentManager);
+                }
             }
 
             if (untracked) // not ours
diff --git 
a/scr/src/test/java/org/apache/felix/scr/integration/ConfigurationChangeTest.java
 
b/scr/src/test/java/org/apache/felix/scr/integration/ConfigurationChangeTest.java
index 52d99c64aa..4d4a971e13 100644
--- 
a/scr/src/test/java/org/apache/felix/scr/integration/ConfigurationChangeTest.java
+++ 
b/scr/src/test/java/org/apache/felix/scr/integration/ConfigurationChangeTest.java
@@ -18,18 +18,29 @@
  */
 package org.apache.felix.scr.integration;
 
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Arrays;
+import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.felix.scr.integration.components.SimpleComponent;
 import org.apache.felix.scr.integration.components.SimpleServiceImpl;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentConstants;
 import org.osgi.service.component.ComponentFactory;
 import org.osgi.service.component.ComponentInstance;
 import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
+import org.osgi.util.tracker.ServiceTracker;
 
 import junit.framework.TestCase;
 
@@ -41,7 +52,7 @@ public class ConfigurationChangeTest extends ComponentTestBase
     static
     {
         // uncomment to enable debugging of this test class
-        //         paxRunnerVmOption = DEBUG_VM_OPTION;
+        //paxRunnerVmOption = DEBUG_VM_OPTION;
 
         descriptorFile = 
"/integration_test_simple_components_configuration_change.xml";
     }
@@ -155,6 +166,19 @@ public class ConfigurationChangeTest extends 
ComponentTestBase
         multipleTest( pid, true );
     }
 
+    @Test
+    public void test_optional_multiple_dynamic_config_cardinality() throws 
Exception
+    {
+        String pid = "test_optional_multiple_dynamic";
+        multipleTestCardinalityDynamic( pid );
+    }
+
+    @Test
+    public void test_optional_multiple_dynamic_config_cardinality_threads() 
throws Exception
+    {
+        String pid = "test_optional_multiple_dynamic";
+        multipleTestCardinalityDynamicThreads( pid );
+    }
 
     @Test
     public void test_required_multiple_dynamic() throws Exception
@@ -205,6 +229,112 @@ public class ConfigurationChangeTest extends 
ComponentTestBase
         multipleTest( pid, false );
     }
 
+    private void multipleTestCardinalityDynamic(String pid) throws Exception
+    {
+        // only register 2 services
+        final SimpleServiceImpl srv1 = SimpleServiceImpl.create( 
bundleContext, "srv1" );
+        final SimpleServiceImpl srv2 = SimpleServiceImpl.create( 
bundleContext, "srv2" );
+
+        // require 3 services
+        theConfig.put("ref.cardinality.minimum", 3);
+        configure( pid );
+        delay();//let cm thread finish before enabling.
+
+        getDisabledConfigurationAndEnable(pid, 
ComponentConfigurationDTO.UNSATISFIED_REFERENCE);
+
+        final SimpleComponent comp10 = SimpleComponent.INSTANCE;
+        TestCase.assertNull( comp10 );
+
+        // create the 3rd service
+        final SimpleServiceImpl srv3 = SimpleServiceImpl.create( 
bundleContext, "srv3" );
+        ServiceTracker<SimpleComponent, SimpleComponent> testTracker = new 
ServiceTracker<>(bundleContext, SimpleComponent.class,  null);
+        testTracker.open();
+
+        // should bind to all 3 services
+        assertNotNull("service is null", testTracker.getService());
+        SimpleComponent comp20 = SimpleComponent.INSTANCE;
+        TestCase.assertEquals( 3, comp20.m_multiRef.size() );
+        TestCase.assertTrue( comp20.m_multiRef.containsAll(Arrays.asList(srv1, 
srv2, srv3)));
+        TestCase.assertEquals( 3, comp20.m_multiRefBind );
+
+        // unregister srv1 to cause deactivation
+        srv1.getRegistration().unregister();
+        
+        TestCase.assertEquals(1, comp20.m_multiRefUnbind);
+        TestCase.assertNull(comp20.m_activateContext);
+
+        // create 4th service to cause reactivation
+        final SimpleServiceImpl srv4 = SimpleServiceImpl.create( 
bundleContext, "srv4" );
+        assertNotNull("service is null", testTracker.getService());
+        // should bind to all 3 services
+        SimpleComponent comp30 = SimpleComponent.INSTANCE;
+        TestCase.assertNotSame( comp20, comp30 );
+        TestCase.assertEquals( 3, comp30.m_multiRef.size() );
+        TestCase.assertTrue( comp30.m_multiRef.containsAll(Arrays.asList(srv2, 
srv3, srv4)));
+        TestCase.assertEquals( 3, comp30.m_multiRefBind );
+    }
+
+    private void multipleTestCardinalityDynamicThreads(String pid) throws 
Exception
+    {
+        // only register 2 services
+        final SimpleServiceImpl srv1 = SimpleServiceImpl.create( 
bundleContext, "srv1" );
+        final SimpleServiceImpl srv2 = SimpleServiceImpl.create( 
bundleContext, "srv2" );
+
+        // require 3 services
+        theConfig.put("ref.cardinality.minimum", 3);
+        configure( pid );
+        delay();//let cm thread finish before enabling.
+
+        getDisabledConfigurationAndEnable(pid, 
ComponentConfigurationDTO.UNSATISFIED_REFERENCE);
+
+        final SimpleComponent comp10 = SimpleComponent.INSTANCE;
+        TestCase.assertNull( comp10 );
+
+        final AtomicReference<SimpleServiceImpl> srv4 = new 
AtomicReference<>();
+        CountDownLatch joiner = new CountDownLatch(2);
+        final AtomicBoolean keepGoing = new AtomicBoolean(true);
+        final ServiceListener listener = new ServiceListener()
+        {
+            @Override
+            public void serviceChanged(ServiceEvent event)
+            {
+                Dictionary<String, Object> props = 
event.getServiceReference().getProperties();
+                if (event.getType() == ServiceEvent.REGISTERED && 
SimpleComponent.class.getName().equals(((Object[])props.get(Constants.OBJECTCLASS))[0]))
 {
+                    if (!keepGoing.compareAndSet(true, false)) {
+                        return;
+                    }
+                    bundleContext.getService(event.getServiceReference());
+
+                    new Thread(() -> {
+                        srv1.getRegistration().unregister();
+                        joiner.countDown();
+                    }, "service unregister").start();
+                    new Thread(() -> {
+                        srv4.set(SimpleServiceImpl.create( bundleContext, 
"srv4" ));
+                        joiner.countDown();
+                    }, "service unregister").start();
+                }
+            }
+        };
+        bundleContext.addServiceListener(listener);
+
+        // create the 3rd service
+        final SimpleServiceImpl srv3 = SimpleServiceImpl.create( 
bundleContext, "srv3" );
+
+        joiner.await();
+        ServiceTracker<SimpleComponent, SimpleComponent> testTracker = new 
ServiceTracker<>(bundleContext, SimpleComponent.class,  null);
+        testTracker.open();
+        assertNotNull("service is null", testTracker.getService());
+        // should bind to 3 services
+        SimpleComponent comp20 = SimpleComponent.INSTANCE;
+        TestCase.assertNotNull(comp20);
+        TestCase.assertNotSame(comp10, comp20);
+        TestCase.assertEquals( 3, comp20.m_multiRef.size() );
+        TestCase.assertTrue("Wrong set of services: " + comp20.m_multiRef, 
comp20.m_multiRef.containsAll(Arrays.asList(srv2, srv3, srv4.get())));
+        TestCase.assertEquals( 3, comp20.m_multiRefBind );
+
+    }
+
     private void multipleTest(String pid, boolean dynamic) throws Exception
     {
         final SimpleServiceImpl srv1 = SimpleServiceImpl.create( 
bundleContext, "srv1" );
diff --git 
a/scr/src/test/resources/integration_test_simple_components_configuration_change.xml
 
b/scr/src/test/resources/integration_test_simple_components_configuration_change.xml
index ef6b989c87..5b9678dc3b 100644
--- 
a/scr/src/test/resources/integration_test_simple_components_configuration_change.xml
+++ 
b/scr/src/test/resources/integration_test_simple_components_configuration_change.xml
@@ -150,8 +150,12 @@
     <scr:component name="test_optional_multiple_dynamic"
         enabled="false"
         configuration-policy="require"
-        modified="modified">
+        modified="modified"
+        immediate="true">
         <implementation 
class="org.apache.felix.scr.integration.components.SimpleComponent" />
+        <service>
+            <provide 
interface='org.apache.felix.scr.integration.components.SimpleComponent' />
+        </service>
         <reference
             name="ref"
             
interface="org.apache.felix.scr.integration.components.SimpleService"

Reply via email to