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

sseifert pushed a commit to branch 
feature/SLING-13146-adapter-manager-child-thread-master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-sling-mock.git

commit 47f2df568a899ff62984d11cfaeb7e910615163f
Author: Stefan Seifert <[email protected]>
AuthorDate: Tue Mar 31 14:38:09 2026 +0200

    SLING-13146 ThreadsafeMockAdapterManagerWrapper: Reuse bundle context for 
child threads
---
 core/pom.xml                                       |  6 ++
 .../sling/ThreadsafeMockAdapterManagerWrapper.java | 37 +++++++++--
 .../ThreadsafeMockAdapterManagerWrapperTest.java   | 76 ++++++++++++++++++++++
 3 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/core/pom.xml b/core/pom.xml
index 3ba54f0..7ec4186 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -305,6 +305,12 @@
             <version>1.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <version>4.3.0</version>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
diff --git 
a/core/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
 
b/core/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
index 52e085d..d55a81e 100644
--- 
a/core/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
+++ 
b/core/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
@@ -48,9 +48,8 @@ class ThreadsafeMockAdapterManagerWrapper implements 
AdapterManager {
                 @Override
                 protected AdapterManagerBundleContextFactory childValue(
                         AdapterManagerBundleContextFactory parentValue) {
-                    // Create a new instance for child threads instead of 
sharing the parent's instance
-                    // This prevents race conditions when parent and child 
threads have different lifecycles
-                    return new AdapterManagerBundleContextFactory();
+                    // create new factory for child thread, taking over bundle 
context from parent thread if available
+                    return new AdapterManagerBundleContextFactory(parentValue);
                 }
             };
 
@@ -81,10 +80,24 @@ class ThreadsafeMockAdapterManagerWrapper implements 
AdapterManager {
 
     private static class AdapterManagerBundleContextFactory {
 
+        private AdapterManagerBundleContextFactory parent;
         private BundleContext bundleContext;
 
+        AdapterManagerBundleContextFactory() {
+            // default constructor
+        }
+
+        AdapterManagerBundleContextFactory(AdapterManagerBundleContextFactory 
parent) {
+            this.parent = parent;
+            this.bundleContext = parent.bundleContext;
+        }
+
         public void setBundleContext(@NotNull final BundleContext 
bundleContext) {
-            log.debug("Set bundle context for AdapterManager, 
bundleContext={}", bundleContext);
+            log.debug(
+                    "Set bundle context for AdapterManager, bundleContext={}, 
factory={}, parent={}",
+                    bundleContext,
+                    this,
+                    parent);
             this.bundleContext = bundleContext;
 
             // register adapter manager
@@ -96,6 +109,11 @@ class ThreadsafeMockAdapterManagerWrapper implements 
AdapterManager {
         }
 
         public void clearBundleContext() {
+            log.debug(
+                    "Clear bundle context for AdapterManager, 
bundleContext={}, factory={}, parent={}",
+                    bundleContext,
+                    this,
+                    parent);
             this.bundleContext = null;
         }
 
@@ -104,12 +122,19 @@ class ThreadsafeMockAdapterManagerWrapper implements 
AdapterManager {
             if (bundleContext == null) {
                 BundleContext newBundleContext = MockOsgi.newBundleContext();
                 log.warn(
-                        "Create new bundle context for adapter manager because 
it was null, bundleContext={}",
-                        bundleContext);
+                        "Create new bundle context for adapter manager because 
it was null, bundleContext={}, factory={}, parent={}",
+                        bundleContext,
+                        this,
+                        parent);
                 setBundleContext(newBundleContext);
             }
             ServiceReference<AdapterManager> serviceReference = 
bundleContext.getServiceReference(AdapterManager.class);
             if (serviceReference != null) {
+                log.trace(
+                        "Get AdapterManager service from bundle context, 
bundleContext={}, factory={}, parent={}",
+                        bundleContext,
+                        this,
+                        parent);
                 return bundleContext.getService(serviceReference);
             } else {
                 throw new RuntimeException("AdapterManager not registered in 
bundle context.");
diff --git 
a/core/src/test/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapperTest.java
 
b/core/src/test/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapperTest.java
new file mode 100644
index 0000000..af26ffb
--- /dev/null
+++ 
b/core/src/test/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapperTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling;
+
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertNotNull;
+
+public class ThreadsafeMockAdapterManagerWrapperTest {
+
+    @Rule
+    public final SlingContext context = new 
SlingContextBuilder(ResourceResolverType.RESOURCEPROVIDER_MOCK)
+            .registerSlingModelsFromClassPath(false)
+            .build();
+
+    @Before
+    public void setUp() {
+        context.registerAdapter(Resource.class, AdapterClass.class, new 
AdapterClass());
+    }
+
+    @Test
+    public void testAdaptionInChildThread() {
+        
assertNotNull(context.create().resource("/content/test").adaptTo(AdapterClass.class));
+        final MyService myService = context.registerService(new MyService());
+        final ScheduledExecutorService executor = 
Executors.newScheduledThreadPool(10);
+        executor.schedule(
+                () -> myService.onChange(
+                        
Objects.requireNonNull(context.resourceResolver().getResource("/content/test"))),
+                2,
+                TimeUnit.SECONDS);
+        await().until(() -> myService.getValue() != null);
+    }
+
+    private static class AdapterClass {}
+
+    private static class MyService {
+        private AdapterClass value;
+
+        public AdapterClass getValue() {
+            return this.value;
+        }
+
+        public void onChange(@NotNull Resource list) {
+            final AdapterClass result = list.adaptTo(AdapterClass.class);
+            value = result;
+        }
+    }
+}

Reply via email to