Injected WorkflowContext vs ThreadLocal

Hi,
I was wondering about the design decision to go with a thread-local based Workflow in the Java SDK. Since the lifecycle of the workflow handler instance is controlled by temporal, wouldn’t this design be more natural to Java developers:

@WorkflowInterface
public class MyWorkflow {
    @Context
    Logger logger;

    @Context
    Workflow ctx;

    @WorkflowMethod
    public void main() {
       // not using static method anymore
        ctx.sleep(100);
    }
}

The @Context annotation would be reusable in actions and can do the proper thing based on the field type - for example replace ThreadLocals with WorkflowThreadLocal, populate loggers with Workflow.getLogger(), etc.

I am assuming that a workload class instance will be serving just one execution - unlike a servlet. I’m new to Temporal so probably I’m missing something :slight_smile:

The core design that uses thread locals allows to implement workflows without relying on a IoC container.

I agree that a lot of Java developers are using one of such containers and are used to dependency injection. We do plan provide integrations with such containers in the future and the code similar to your sample will be possible.

I believe Spring Boot is the most requested integration.

Do we have this feature now? I came here looking for a way to pass a Spring container managed Bean into Temporal workflow. Is there any way?

Yes, Temporal provides Spring Boot integration. But it doesn’t support injection into workflow code. This is done on purpose as injecting external objects into the workflow can lead to determinism errors.

I want to create a database object lock before I start any of the workflows and release that lock after all the workflows are over. How can I achieve that without being able to inject an object of lockRegistry inside the workflow?

Because all the workflows are independently running and only the the parent workflow has end to end child workflows defined.

Does temporal provide anyway to acquire and release db locks?

Temporal workflows cannot perform any IO directly to support deterministic execution.

They have to perform all IO through activities. To acquire and release DB locks, implement correspondent activities, and invoke them from the workflow code. Temporal supports injecting dependencies into activities.

What is your business use case? Why do you need to talk to a DB and especially keep long locks?

1 Like

I see. We need to run a long execution and make sure the locks are in place to avoid someone else triggering the same execution.

Also I cannot use the activities to execute this IO because Activities and Workflows are 2 different microservices. The moment I use activities, I have to serialize and deserialize the Lock object to share between the microservices which doesn’t work.

Do you recommend keeping Workflows and Activities in the same microservice? I don’t know why it was designed like this.

You don’t need to use an external DB to ensure the uniqueness of Temporal workflows. They are guaranteed to be unique per WorkflowID out of the box.

Temporal supports routing activities to specific processes and hosts. So, it supports use cases that require some activities to be executed on the same host. This is not needed for your use case, but for cases like media processing, it is necessary.

There is one legacy app that runs the same code (Workflows) sequentially (not using temporal but plain spring framework) and what I am working on is the new revised app. I want to acquire a lock to provide mutual exclusion between both the apps. This is business requirement to be honest. Seems like I am stuck with the bad design.

Is it possible to communicate with Temporal from the old app? Then, you can use Temporal workflow as a lock. Or locking through DB is the only option that the old app supports?

The old application is agnostic to the new app and Temporal framework/workflows. So database is the only way to maintain the locks. In the old app the spring’s JdbcLockregistry was used considering mutual exclusion at DB layer. Can ContextPropogater help me here? I would acquire the lock and store the Lock object inside this if it work. Unless we have to serialize and deserialize objects.

Alternatively, is it possible to acquire a lock and release it before I start my workflow sessions? Right now this is how we start the main session,


 WorkflowClient.start(myTaskSessionWorkflow::executeMyTaskSession,
                        myTaskSessionWorkflowInitRequest);

However, the thread comes out after starting the execution. This is likely not possible given how Temporal works but is it possible to remain in this thread until the executeMyTaskSession is completed? May be with some other manner in which I can start workflow sessions?
Something like the below?

Lock lock = null;
try {
//acquire lock
 WorkflowClient.start(myTaskSessionWorkflow::executeMyTaskSession,
                        myTaskSessionWorkflowInitRequest);
} finally {
lock.unlock();
}

ETA: Looks like java does not have ContextPropogater only go and typescript SDKs have it Search the documentation | Temporal Documentation

ETA 2 - It works with the below code. So I have to manage my logic of locking and unlocking outside the workflows.

 var lock = lockRegistry.obtain(lockKey);
                try {
                    lock.lock();
                    // start sync and wait for results (or failure)
                    String result = myTaskSessionWorkflow.executeMyTaskSession(myTaskSessionWorkflowInitRequest);
                    LOGGER.info("Result {}", result);
                } finally {
                    lock.unlock();
                }

Thank you so much Maxim for answering my queries over here. :slight_smile:

Ref: Foundations - Java SDK feature guide | Temporal Documentation

What does happen currently when the process that took the lock crashes?

That’s a great question. In that case the lock will auto release in 1 hour. This is more like a lease agreement than locking agreement with spring integration library. There is a TTL set for each lock.

So, is there no way to release this lock by running a separate command/process?

Unfortunately, no. Locks need to be released in the same thread they were starting from. I can technically release them by directly hitting the DB and deleting them but that’s a poor design and other business use cases may get impacted.

I see. Then, I would implement an activity that takes the lock and releases it when canceled. This activity heartbeats while keeping the lock to notify Temporal that it is still alive. When the activity fails to heartbeat the workflow will get an exception and perform whatever cleanup logic possible.