Chapter 10.  Asynchronous Continuations


10.1. The Concept

jBPM is based on Graph-Oriented Programming (GOP). Basically, GOP specifies a simple-state machine that can handle concurrent paths of execution but, in the specified execution algorithm, all state transitions are undertaken in a single thread client operation. By default, it is a good approach to perform state transitions in the thread of the client because it fits naturally with server-side transactions. The process execution moves from one "wait" state to another in the space of one transaction.
In some situations, a developer might want to fine-tune the transaction demarcation in the process definition. In jPDL, it is possible to specify that the process execution should continue asynchronously with the attribute async="true". async="true" is supported only when it is triggered in an event but can be specified on all node types and all action types.

10.2. Example

Normally, a node is always executed after a token has entered it. Hence, the node is executed in the client's thread. One will explore asynchronous continuations by looking at two examples. The first example is part of a process with three nodes. Node 'a' is a wait state, node 'b' is an automated step and node 'c' is, again, a wait state. This process does not contain any asynchronous behavior and it is represented in the diagram below.
The first frame shows the starting situation. The token points to node 'a', meaning that the path of execution is waiting for an external trigger. That trigger must be given by sending a signal to the token. When the signal arrives, the token will be passed from node 'a' over the transition to node 'b'. After the token arrived in node 'b', node 'b' is executed. Recall that node 'b' is an automated step that does not behave as a wait state (e.g. sending an email). So the second frame is a snapshot taken when node 'b' is being executed. Since node 'b' is an automated step in the process, the execute of node 'b' will include the propagation of the token over the transition to node 'c'. Node 'c' is a wait state so the third frame shows the final situation after the signal method returns.
Example One: Process without Asynchronous Continuation

Figure 10.1. Example One: Process without Asynchronous Continuation

Whilst "persistence" is not mandatory in jBPM, most commonly a signal will be called within a transaction. Look at the updates of that transaction. Initially, the token is updated to point to node 'c'. These updates are generated by Hibernate as a result of the GraphSession.saveProcessInstance on a JDBC connection. Secondly, in case the automated action accesses and updates some transactional resources, such updates should be combined or made part of the same transaction.
The second example is a variant of the first and introduces an asynchronous continuation in node 'b'. Nodes 'a' and 'c' behave the same as in the first example, namely they behave as wait states. In jPDL a node is marked as asynchronous by setting the attribute async="true".
The result of adding async="true" to node 'b' is that the process execution will be split into two parts. The first of these will execute the process up to the point at which node 'b' is to be executed. The second part will execute node 'b.' That execution will stop in wait state 'c'.
The transaction will hence be split into two separate transactions, one for each part. While it requires an external trigger (the invocation of the Token.signal method) to leave node 'a' in the first transaction, jBPM will automatically trigger and perform the second transaction.
A Process with Asynchronous Continuations

Figure 10.2. A Process with Asynchronous Continuations

For actions, the principle is similar. Actions that are marked with the attribute async="true" are executed outside of the thread that executes the process. If persistence is configured (it is by default), the actions will be executed in a separate transaction.
In jBPM, asynchronous continuations are realized by using an asynchronous messaging system. When the process execution arrives at a point that should be executed asynchronously, jBPM will suspend the execution, produces a command message and send it to the command executor. The command executor is a separate component that, upon receipt of a message, will resume the execution of the process where it got suspended.
jBPM can be configured to use a JMS provider or its built-in asynchronous messaging system. The built-in messaging system is quite limited in functionality, but allows this feature to be supported on environments where JMS is unavailable.

10.3. The Job Executor

The job executor is the component that resumes process executions asynchronously. It waits for job messages to arrive over an asynchronous messaging system and executes them. The two job messages used for asynchronous continuations are ExecuteNodeJob and ExecuteActionJob.
These job messages are produced by the process execution. During process execution, for each node or action that has to be executed asynchronously, a Job (Plain Old Java Object) will be dispatched to the MessageService. The message service is associated with the JbpmContext and it just collects all the messages that have to be sent.
The messages will be sent as part of JbpmContext.close(). That method cascades the close() invocation to all of the associated services. The actual services can be configured in jbpm.cfg.xml. One of the services, JmsMessageService, is configured by default and will notify the job executor that new job messages are available.
The graph execution mechanism uses the interfaces MessageServiceFactory and MessageService to send messages. This is to make the asynchronous messaging service configurable (also in jbpm.cfg.xml). In Java EE environments, the DbMessageService can be replaced with the JmsMessageService to leverage the application server's capabilities.
The following is a brief summary of the way in which the job executor works.
"Jobs" are records in the database. Furthermore, they are objects and can be executed. Both timers and asynchronous messages are jobs. For asynchronous messages, the dueDate is simply set to the current time when they are inserted. The job executor must execute the jobs. This is done in two phases.
  • The dispatcher thread must acquire a job
  • An executor thread must execute the job
Acquiring a job and executing the job are done in 2 separate transactions. The dispatcher thread acquires jobs from the database on behalf of all the executor threads on this node. When the executor thread takes the job, it adds its name into the owner field of the job. Each thread has a unique name based on IP address and sequence number.
A thread could fail between acquisition and execution of a job. To clean-up after those situations, there is one lock-monitor thread per job executor that checks the lock times. The lock monitor thread will unlock any jobs that have been locked for more than 10 minutes, so that they can be executed by another job executor thread.
The isolation level must be set to REPEATABLE_READ for Hibernate's optimistic locking to work correctly. REPEATABLE_READ guarantees that this query will only update one row in exactly one of the competing transactions.
update JBPM_JOB job
set job.version = 2
    job.lockOwner = '192.168.1.3:2'
where 
    job.version = 1
Non-Repeatable Reads can lead to the following anomaly. A transaction re-reads data it has previously read and finds that data has been modified by another transaction, one that has been committed since the transaction's previous read.
Non-Repeatable reads are a problem for optimistic locking and therefore, isolation level READ_COMMITTED is not enough because it allows for Non-Repeatable reads to occur. So REPEATABLE_READ is required if you configure more than one job executor thread.
Configuration properties related to the job executor are:
jbpmConfiguration
The bean from which configuration is retrieved.
name
The name of this executor.

Important

This name should be unique for each node, when more than one jBPM instance is started on a single machine.
nbrOfThreads
The number of executor threads that are started.
idleInterval
The interval that the dispatcher thread will wait before checking the job queue, if there are no jobs pending.

Note

The dispatcher thread is automatically notifed when jobs are added to the queue.
retryInterval
The interval that a job will wait between retries, if it fails during execution. The default value for this is 3 times.

Note

The maximum number of retries is configured by jbpm.job.retries.
maxIdleInterval
The maximum period for idleInterval.
historyMaxSize
This property is deprecated and has no effect.
maxLockTime
The maximum time that a job can be locked before the lock-monitor thread will unlock it.
lockMonitorInterval
The period for which the lock-monitor thread will sleep between checking for locked jobs.
lockBufferTime
This property is deprecated, and has no affect.

10.4. jBPM's built-in asynchronous messaging

When using jBPM's built-in asynchronous messaging, job messages will be sent by persisting them to the database. This message persisting can be done in the same transaction or JDBC connection as the jBPM process updates.
The job messages will be stored in the JBPM_JOB table.
The POJO command executor (org.jbpm.msg.command.CommandExecutor) will read the messages from the database table and execute them. The typical transaction of the POJO command executor looks like this:
  1. Read next command message
  2. Execute command message
  3. Delete command message
If execution of a command message fails, the transaction will be rolled back. After that, a new transaction will be started that adds the error message to the message in the database. The command executor filters out all messages that contain an exception.
POJO command executor transactions

Figure 10.3. POJO command executor transactions

If the transaction that adds the exception to the command message fails, it is rolled back. The message will remain in the queue without an exception and will be retried later.

Important

jBPM's built-in asynchronous messaging system does not support multi-node locking. You cannot deploy the POJO command executor multiple times and have them configured to use the same database.
Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.