-
Bug Report
-
Resolution: Fixed
-
L3 - Default
-
7.4.0
Steps to reproduce:
1. There are two engines, both configured against the same database
2. Engine 1 deploys a process with an async task called X
3. Engine 2 deploys a new version of that process where X is removed
4. Loading the new process definition into engine 1's cache fails with a NullPointerException (stacktrace below)
Alternative reproduction steps for a single engine:
1. Deploy process with an async task called X
2. Deploy new version of that process where X is removed
3. Clear deployment cache
4. Loading the new process definition into the cache fails with a NullPointerException
Alternative reproduction steps with one engine and multiple threads:
1. Two process definitions - A and B - with the same key X
2. Thread 1 begins loading A into the cache, parses A and puts its job declarations into the shared map (org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer#transformDefinitions)
3. Thread 2 begins loading B into the cache, parses B and puts its job declarations into the shared map(org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer#transformDefinitions)
4. Thread 1 attempts to create job definitions that don't exist yet for definition A. In the shared map, it finds the job declarations for B. Accordingly, it creates job definitions for activities contained in B (but not in A) (org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer#definitionAddedToDeploymentCache)
The error may also occur on server restart, if the first version is loaded into the cache before the second version of the process.
Reason:
- BpmnDeployer keeps a map of all job declarations it ever saw, index by process definition key
- when loading an existing process into the cache, it looks up these declarations by key in order to create job definitions for them (migration logic)
- Legacy migration logic (for multi-instance) assumes that the declarations reference a valid activity in the current process definition
Side note:
- let's consider removing caching all job declarations in the BpmnDeployer. This is not thread-safe (not such a big problem since deployments are synchronized anyway, but not nice either) and a memory leak (we never remove things from the map)
- the map was made a member of BpmnDeployer here (https://github.com/camunda/camunda-bpm-platform/commit/ad5dfda31623a0d27254dedbfed8f529acc910a9#diff-52c3c9ad8a79f7a84e7ba52163b3a9f3), so it might be an oversight in refactoring instead of an on-purpose change
Test Case:
Workaround:
Stacktrace
java.lang.NullPointerException at org.camunda.bpm.engine.impl.pvm.runtime.LegacyBehavior.isAsync(LegacyBehavior.java:536) at org.camunda.bpm.engine.impl.pvm.runtime.LegacyBehavior.migrateMultiInstanceJobDefinitions(LegacyBehavior.java:528) at org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer.updateJobDeclarations(BpmnDeployer.java:187) at org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer.definitionAddedToDeploymentCache(BpmnDeployer.java:152) at org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer.definitionAddedToDeploymentCache(BpmnDeployer.java:1) at org.camunda.bpm.engine.impl.AbstractDefinitionDeployer.registerDefinition(AbstractDefinitionDeployer.java:294) at org.camunda.bpm.engine.impl.AbstractDefinitionDeployer.loadDefinitions(AbstractDefinitionDeployer.java:246) at org.camunda.bpm.engine.impl.AbstractDefinitionDeployer.postProcessDefinitions(AbstractDefinitionDeployer.java:212) at org.camunda.bpm.engine.impl.AbstractDefinitionDeployer.deploy(AbstractDefinitionDeployer.java:58) at org.camunda.bpm.engine.impl.persistence.deploy.DeploymentCache$1.call(DeploymentCache.java:69) at org.camunda.bpm.engine.impl.persistence.deploy.DeploymentCache$1.call(DeploymentCache.java:1) at org.camunda.bpm.engine.impl.interceptor.CommandContext.runWithoutAuthorization(CommandContext.java:553) at org.camunda.bpm.engine.impl.persistence.deploy.DeploymentCache.deploy(DeploymentCache.java:66) at org.camunda.bpm.engine.impl.persistence.deploy.DeploymentCache.resolveProcessDefinition(DeploymentCache.java:140) at org.camunda.bpm.engine.impl.persistence.deploy.DeploymentCache.findDeployedLatestProcessDefinitionByKey(DeploymentCache.java:102) at org.camunda.bpm.engine.impl.cmd.StartProcessInstanceCmd.execute(StartProcessInstanceCmd.java:63) at org.camunda.bpm.engine.impl.cmd.StartProcessInstanceCmd.execute(StartProcessInstanceCmd.java:1) at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24) at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:95) at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:30) at org.camunda.bpm.engine.impl.RuntimeServiceImpl.startProcessInstanceByKey(RuntimeServiceImpl.java:66) at org.camunda.bpm.engine.test.bpmn.async.AsyncTaskTest.testDeployAndRemoveAsyncActivity(AsyncTaskTest.java:746) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at junit.framework.TestCase.runTest(TestCase.java:176) at junit.framework.TestCase.runBare(TestCase.java:141) at org.camunda.bpm.engine.impl.test.AbstractProcessEngineTestCase.runBare(AbstractProcessEngineTestCase.java:99) at junit.framework.TestResult$1.protect(TestResult.java:122) at junit.framework.TestResult.runProtected(TestResult.java:142) at junit.framework.TestResult.run(TestResult.java:125) at junit.framework.TestCase.run(TestCase.java:129) at junit.framework.TestSuite.runTest(TestSuite.java:255) at junit.framework.TestSuite.run(TestSuite.java:250) at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:84) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)