Publié le

ResourceResolverFactoryActivator Timeout: Fixing a Flaky AEMaaCS JCR Mock Startup


Some build failures are straightforward. This one was not.

The same AEMaaCS unit test suite would pass in one pipeline run and fail in the next, with no code changes in between. That kind of randomness usually sends you looking for test order issues, shared state, or a race condition in your own code.

One important detail: I never saw this on local builds, and I also never saw it on Azure Pipeline for the same project when it ran against AEM OnPrem.

That was the clue. The application was not the problem. The failure was happening while the test harness was building the AEM mock context.

The Symptom

The pipeline log looked like this:

14:44:31,014 [ThreadedStreamConsumer] [ERROR] com.example.ExampleTest.test(AemContext) -- Time elapsed: 16.08 s <<< ERROR!
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [io.wcm.testing.mock.aem.junit5.JcrMockAemContext arg0] in method [void com.example.ExampleTest.beforeEach(io.wcm.testing.mock.aem.junit5.JcrMockAemContext)]: Could not create io.wcm.testing.mock.aem.junit5.JcrMockAemContext instance.
...
Caused by: java.lang.RuntimeException: Unable to initialize JCR_MOCK resource resolver factory: ResourceResolverFactoryActivator did not register a ResourceResolverFactory after 500ms.
...
Caused by: java.lang.IllegalStateException: ResourceResolverFactoryActivator did not register a ResourceResolverFactory after 500ms.

At first glance, this looks like a broken mock setup or a shaky test fixture. The useful detail is at the bottom: the JCR mock environment gave up after 500ms.

So this was not a business logic bug. It was a timeout problem in the test infrastructure.

What Was Actually Failing

The exception comes from org.apache.sling.testing.mock.sling.ResourceResolverFactoryInitializer. Its job is simple: start the mock resource resolver factory and wait until the OSGi activator shows up.

The relevant code path is essentially this:

class ResourceResolverFactoryInitializer {

    private static final Logger log = LoggerFactory.getLogger(ResourceResolverFactoryInitializer.class);

    private static final String SYSTEM_PROPERTY_RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS =
            "sling.mock.resourceresolverfactoryactivator.timeout.ms";
    private static final long RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS;

    static {
        RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS = NumberUtils.toLong(
                System.getProperty(SYSTEM_PROPERTY_RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS, "500"));
    }

    /**
     * Initialize resource resolver factory activator.
     * @param bundleContext Bundle context
     */
    private static void initializeResourceResolverFactoryActivator(@NotNull BundleContext bundleContext) {
        Map<String, Object> config = new HashMap<>();
        // do not required a specific resource provider (otherwise "NONE" will not work)
        config.put("resource.resolver.required.providers", "");
        config.put("resource.resolver.required.providernames", "");
        MockOsgi.registerInjectActivateService(ResourceResolverFactoryActivator.class, bundleContext, config);

        // wait until ResourceResolverFactory appears as service - since SLING-12019 this is done asynchronously
        final long startTime = System.currentTimeMillis();
        while (bundleContext.getServiceReference(ResourceResolverFactory.class) == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
            if (System.currentTimeMillis() - startTime > RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS) {
                throw new IllegalStateException(
                        "ResourceResolverFactoryActivator did not register a ResourceResolverFactory after "
                                + RESOURCERESOLVER_FACTORY_ACTIVATOR_TIMEOUT_MS + "ms.");
            }
        }
    }
}

The important part is that the timeout is configurable through a system property, and the default is only 500ms.

That can be fine on a fast local machine. In CI, it is brittle.

Why It Felt Random

The failure was occasional because two things were competing:

  • the mock OSGi environment registering ResourceResolverFactory
  • the 500ms wait loop expiring

If the worker was lightly loaded, registration finished in time. If it was busy, the test slipped past the timeout and failed.

That is what makes this kind of issue annoying: the application code stays the same, but the environment is sitting right on a timing edge.

In AEMaaCS pipelines, that is easy to hit. Shared runners, parallel test execution, JVM warmup, and container startup all add a little delay. When the limit is only half a second, small variations turn into flaky builds.

The Fix

The fix was simple: give the mock environment more time to initialize by setting the timeout system property in Maven Surefire.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <useSystemClassLoader>false</useSystemClassLoader>
        <systemPropertyVariables>
            <sling.mock.resourceresolverfactoryactivator.timeout.ms>10000</sling.mock.resourceresolverfactoryactivator.timeout.ms>
        </systemPropertyVariables>
    </configuration>
</plugin>

With that in place, the mock framework had 10 seconds instead of 500ms to complete the registration.

That sounds like a big jump, but it really just moves the test harness away from a fragile boundary. The test still exercises the same behavior; it just stops failing because the mock runtime was a little slow to start.

How I Verified It

To make sure the fix was real, I ran the Maven test goal in debug mode and confirmed that the JVM picked up the system property from pom.xml.

That mattered because it ruled out the usual trap: setting the property in the build file, but never actually passing it into the test JVM.

Once the property was visible at runtime, the flaky JcrMockAemContext startup failures disappeared.