Get business key from delegateExecution fails with NullPointerException for True Async tasks

Hi, Folks,
For true async service tasks it seems delegateExecution.getProcessInstanceBusinessKey() fails with null pointer exception.
This one seems to fail only on certain async tasks.
The process is extremely simple fork/join with 2 parralel true async service tasks.

java.lang.NullPointerException
	at org.flowable.engine.impl.util.CommandContextUtil.getExecutionEntityManager(CommandContextUtil.java:471)
	at org.flowable.engine.impl.util.CommandContextUtil.getExecutionEntityManager(CommandContextUtil.java:467)
	at org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.ensureProcessInstanceInitialized(ExecutionEntityImpl.java:489)
	at org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.getProcessInstance(ExecutionEntityImpl.java:483)
	at org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.getProcessInstanceBusinessKey(ExecutionEntityImpl.java:403)
	at com.paysafe.ss.flowable.common.context.BaseReadOnlyProcessContext.getProcessInstanceBusinessKey(BaseReadOnlyProcessContext.java:77)
	at com.paysafe.ss.flowable.common.ProcessLogger$ProcessExecutionLogEntry.create(ProcessLogger.java:73)
	at com.paysafe.ss.flowable.common.TimeTrackingExecutor.executeTask(TimeTrackingExecutor.java:31)
	at com.paysafe.ss.flowable.common.AsyncJavaTask.execute(AsyncJavaTask.java:33)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.tryInvoke(MockMethodAdvice.java:213)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.access$400(MockMethodAdvice.java:35)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice$RealMethodCall.invoke(MockMethodAdvice.java:165)
	at org.mockito.internal.invocation.InterceptedInvocation.callRealMethod(InterceptedInvocation.java:152)
	at org.mockito.internal.stubbing.answers.CallsRealMethods.answer(CallsRealMethods.java:44)
	at org.mockito.Answers.answer(Answers.java:100)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:103)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:33)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:61)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.handle(MockMethodAdvice.java:106)
	at com.paysafe.ss.flowable.common.AsyncJavaTask.execute(AsyncJavaTask.java:33)
	at com.paysafe.ss.flowable.common.AsyncJavaTask.execute(AsyncJavaTask.java:22)
	at org.flowable.engine.delegate.FlowableFutureJavaDelegate.lambda$execute$0(FlowableFutureJavaDelegate.java:35)
	at org.flowable.common.engine.impl.async.DefaultAsyncTaskExecutor.lambda$submit$0(DefaultAsyncTaskExecutor.java:115)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" id="definitions" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
  <process id="asyncProcess" name="asyncProcess">
    <startEvent id="start"/>
    <sequenceFlow id="start_flow1" sourceRef="start" targetRef="simpleServiceTask1"/>
    <serviceTask id="simpleServiceTask1" name="Simple Service Task" flowable:delegateExpression="${simpleServiceTask1}"/>
    <!-- Fork gateway -->
    <sequenceFlow id="parallelGateway_flow1" sourceRef="sid-1ac52630-cf71-424f-be0d-52d5e7025f34" targetRef="simpleServiceTask2"/>
    <sequenceFlow id="parallelGateway_flow2" sourceRef="sid-1ac52630-cf71-424f-be0d-52d5e7025f34" targetRef="simpleAsyncServiceTask"/>
    <serviceTask id="simpleServiceTask2" name="Simple Service Task 2" flowable:delegateExpression="${simpleServiceTask2}" flowable:exclusive="true" flowable:async="false">
      <extensionElements>
        <flowable:failedJobRetryTimeCycle>R3/PT5S</flowable:failedJobRetryTimeCycle>
      </extensionElements>
    </serviceTask>
    <serviceTask id="simpleAsyncServiceTask" name="Simple Async Service Task" flowable:delegateExpression="${simpleAsyncServiceTask}" flowable:exclusive="true" flowable:async="true">
      <extensionElements>
        <flowable:failedJobRetryTimeCycle>R2/PT1S</flowable:failedJobRetryTimeCycle>
      </extensionElements>
    </serviceTask>
    <sequenceFlow id="simpleServiceTask3_flow" sourceRef="simpleAsyncServiceTask" targetRef="theEnd"/>
    <!-- Join gateway -->
    <endEvent id="theEnd"/>
    <sequenceFlow id="sid-2830aa1d-25fe-4723-893c-c899a1661a5d" sourceRef="simpleServiceTask2" targetRef="theEnd"/>
    <parallelGateway id="sid-1ac52630-cf71-424f-be0d-52d5e7025f34"/>
    <sequenceFlow id="sid-682c41a0-382e-456c-b5ac-ef9e7e29390e" sourceRef="simpleServiceTask1" targetRef="sid-1ac52630-cf71-424f-be0d-52d5e7025f34"/>
  </process>
</definitions>

After some debugging i found, that for accessing the process engine configuration, it seems:
ExecutionEntityImpl tries to get ExecutionEntityManager from some thread local variables:

    public static ExecutionEntityManager getExecutionEntityManager() {
        return getExecutionEntityManager(getCommandContext());
    }

    public static CommandContext getCommandContext() {
        return Context.getCommandContext();
    }

Which tries to get ProcessEngine Configuration from thread local variable.
It seems to me that this CommandContext is not set upon spawning the async thread or not setup properly.

What’s the implementation of those async classes? Are you using your own threadpooling? If so, then indeed that will not work. In any way, check True Parallel Service Task Execution with Flowable which describes the way to achieve what you want.

Some default Thread Pool of flowable is used. Will check and get back to you.