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

joerghoh pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly.git


The following commit(s) were added to refs/heads/master by this push:
     new d2024ca  SLING-12471 - The ScriptDependencyResolver can wrongly return 
null values when invoked concurrently (#28)
d2024ca is described below

commit d2024ca05e50bc02ae37a068577cc1b04e32a471
Author: Jörg Hoh <[email protected]>
AuthorDate: Wed Nov 6 16:34:06 2024 +0100

    SLING-12471 - The ScriptDependencyResolver can wrongly return null values 
when invoked concurrently (#28)
    
    * SLING-12471 - The ScriptDependencyResolver can wrongly return null values 
when invoked concurrently
    
    Co-authored-by: Radu Cotescu <[email protected]>
---
 .../impl/utils/ScriptDependencyResolver.java       |  10 +-
 ...=> ScriptDependencyResolverConcurrentTest.java} | 101 +++++++++------------
 .../impl/utils/ScriptDependencyResolverTest.java   |   4 +-
 3 files changed, 49 insertions(+), 66 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolver.java
 
b/src/main/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolver.java
index f89c6fb..ad3f29d 100644
--- 
a/src/main/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolver.java
+++ 
b/src/main/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolver.java
@@ -145,11 +145,11 @@ public class ScriptDependencyResolver implements 
ResourceChangeListener, Externa
                 } finally {
                     writeLock.unlock();
                 }
-            } else {
-                String scriptPath = resolutionCache.get(cacheKey);
-                if (!scriptPath.equals(NOT_FOUND_MARKER)) {
-                    result = 
scriptingResourceResolverProvider.getRequestScopedResourceResolver().getResource(scriptPath);
-                }
+            }
+            String scriptPath = resolutionCache.get(cacheKey);
+            // in case another thread was downgrading the lock, we need to 
recheck the value
+            if (result == null && scriptPath != null && 
!scriptPath.equals(NOT_FOUND_MARKER)) {
+                result = 
scriptingResourceResolverProvider.getRequestScopedResourceResolver().getResource(scriptPath);
             }
             return result;
         } finally {
diff --git 
a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
 
b/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverConcurrentTest.java
similarity index 58%
copy from 
src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
copy to 
src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverConcurrentTest.java
index 4123ee3..e8f77ab 100644
--- 
a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
+++ 
b/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverConcurrentTest.java
@@ -18,20 +18,22 @@
  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.scripting.sightly.impl.utils;
 
+import javax.script.Bindings;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
-import javax.script.Bindings;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
-import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.scripting.SlingBindings;
 import 
org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider;
 import 
org.apache.sling.scripting.sightly.impl.engine.SightlyEngineConfiguration;
@@ -40,19 +42,25 @@ import 
org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 
-public class ScriptDependencyResolverTest {
-
-    private ScriptDependencyResolver scriptDependencyResolver;
+@RunWith(Parameterized.class)
+public class ScriptDependencyResolverConcurrentTest {
+    
+    @Parameterized.Parameters
+    public static Object[] data() {
+        return new Object[20][0];
+    }
+    
+    
 
     @Rule
     public SlingContext context = new SlingContext();
@@ -76,34 +84,7 @@ public class ScriptDependencyResolverTest {
     }
 
     @Test
-    public void testDependenciesResolvingCacheDisabled() {
-        SightlyEngineConfiguration configuration = 
mock(SightlyEngineConfiguration.class);
-        when(configuration.getScriptResolutionCacheSize()).thenReturn(0);
-        context.registerService(configuration);
-
-        ResourceResolver scriptingResolver = spy(context.resourceResolver());
-
-        ScriptingResourceResolverProvider scriptingResourceResolverProvider =
-                mock(ScriptingResourceResolverProvider.class);
-        
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(scriptingResolver);
-        context.registerService(ScriptingResourceResolverProvider.class, 
scriptingResourceResolverProvider);
-
-        scriptDependencyResolver = context.registerInjectActivateService(new 
ScriptDependencyResolver());
-
-        RenderContext renderContext = mock(RenderContext.class);
-        Bindings bindings = mock(Bindings.class);
-        when(renderContext.getBindings()).thenReturn(bindings);
-        
when(bindings.get(SlingBindings.REQUEST)).thenReturn(context.request());
-
-        Resource partial = 
scriptDependencyResolver.resolveScript(renderContext, "partial.html");
-        assertNotNull(partial);
-        partial = scriptDependencyResolver.resolveScript(renderContext, 
"partial.html");
-        assertNotNull(partial);
-        verify(scriptingResolver, times(8)).getResource(anyString());
-    }
-
-    @Test
-    public void testDependenciesResolvingCacheEnabled() {
+    public void 
testDependenciesResolvingCacheEnabledConcurrent_neverReturnsNull() throws 
InterruptedException, ExecutionException  {
         SightlyEngineConfiguration configuration = 
mock(SightlyEngineConfiguration.class);
         when(configuration.getScriptResolutionCacheSize()).thenReturn(1024);
         context.registerService(configuration);
@@ -115,34 +96,36 @@ public class ScriptDependencyResolverTest {
         
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(scriptingResolver);
         context.registerService(ScriptingResourceResolverProvider.class, 
scriptingResourceResolverProvider);
 
-        scriptDependencyResolver = context.registerInjectActivateService(new 
ScriptDependencyResolver());
+        ScriptDependencyResolver scriptDependencyResolver = 
spy(context.registerInjectActivateService(new ScriptDependencyResolver()));
 
         RenderContext renderContext = mock(RenderContext.class);
         Bindings bindings = mock(Bindings.class);
         when(renderContext.getBindings()).thenReturn(bindings);
         
when(bindings.get(SlingBindings.REQUEST)).thenReturn(context.request());
+        
+        // invoke resolveScript with the same identifier
+        int size = 100;
+        ExecutorService executor = Executors.newFixedThreadPool(size);
+
+        try {
+            List<Future<Resource>> resolutionValues = new ArrayList<>();
+            for (int i = 0; i < size; i++) {
+                resolutionValues.add(executor.submit(() -> {
+                    return 
scriptDependencyResolver.resolveScript(renderContext, "partial.html");
+                }));
+            }
+            int nullValues = 0;
+            for (Future<Resource> v : resolutionValues) {
+                Resource r = v.get();
+                if (r == null) {
+                    nullValues++;
+                }
+            }
+            assertEquals("resolveScript must never return null here",0, 
nullValues);
+        } finally { 
+            executor.shutdown();
+        }
 
-        Resource partial = 
scriptDependencyResolver.resolveScript(renderContext, "partial.html");
-        assertNotNull(partial);
-        partial = scriptDependencyResolver.resolveScript(renderContext, 
"partial.html");
-        assertNotNull(partial);
-        verify(scriptingResolver, times(5)).getResource(anyString());
-
-        // simulate a change in the resource tree
-        List<ResourceChange> changes = new ArrayList<>();
-        changes.add(mock(ResourceChange.class));
-        scriptDependencyResolver.onChange(changes);
-
-
-        // cache should be empty; another retrieval should increase the 
invocations by 4
-        partial = scriptDependencyResolver.resolveScript(renderContext, 
"partial.html");
-        assertNotNull(partial);
-        verify(scriptingResolver, times(9)).getResource(anyString());
-
-        // cache repopulated; another retrieval should increase the 
invocations only by 1
-        partial = scriptDependencyResolver.resolveScript(renderContext, 
"partial.html");
-        assertNotNull(partial);
-        verify(scriptingResolver, times(10)).getResource(anyString());
     }
 
 }
diff --git 
a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
 
b/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
index 4123ee3..4c4417c 100644
--- 
a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
+++ 
b/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ScriptDependencyResolverTest.java
@@ -19,13 +19,13 @@
 package org.apache.sling.scripting.sightly.impl.utils;
 
 
+import javax.script.Bindings;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.script.Bindings;
-
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;

Reply via email to