fix(bpm): report the step an instance is parked on while an async retry is pending (#7193) - #7212
Merged
Merged
Conversation
…ry is pending (#7193) `getProcessInstanceActiveActivityIds` - and so `GET /services/bpm/bpm-processes/instance/{id}/active` - answered `{}` for a live instance waiting on an asynchronous job that had already failed once. Flowable's `JobRetryCmd` deactivates the execution when it moves the failed job to the timer-job table, and `getActiveActivityIds` collects active executions only, so the position was evidenced by the job alone - while the method's only job query was for dead-letter jobs. Since `BpmnIntentGenerator` emits its steps `flowable:async`, an intent-generated process read as "no step is running" for its whole life: nothing for the Processes viewer or the Monitoring diagram to overlay, and no way for an application to tell its users which step a record is on. Executable and timer jobs now count towards `positive` alongside the active executions, merged by the higher of the two counts rather than their sum (a token that has not failed yet is active and job-bearing, so adding them would report every pending step twice). Dead-letter jobs stay `negative`, and a job bound to no flow element no longer becomes a literal "null" key. Two sites sharing the root cause come with it: the instance diagram PNG highlights the occupied activities instead of only the active ones, and the Monitoring Retry action - which hands a dead-lettered job back to the executor without reactivating the execution - stops making the step look finished. `ProcessInstanceData.activityId` is resolved the same way instead of read off the root process-instance execution, where Flowable always leaves it unset. Covered by `BpmProviderFlowableActiveActivitiesTest`, which drives a real in-memory engine synchronously through pending, parked, dead-lettered and retried states, and by `BpmActiveActivitiesIT`, which parks a published `flowable:async` step between retry attempts and asserts both HTTP answers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Sep 10, 2026
…ies (#7266) #7212 made the listing resolve each instance's activity id instead of reading the always-null one off the instance, at three queries apiece - an execution query and the two job queries. The Monitoring shell's Overview polls the listing with no key and no limit every 30 s, so a tenant with a few hundred running instances paid 3N statements a poll for a column the listing renders as one badge. The resolution is now done for the whole listing at once: one execution query over all the listed ids, and the two job queries over the current tenant - Flowable's job queries take a single process-instance id, so the tenant is the narrowest batch filter there is - grouped by process instance in memory. The evidence and the answer are unchanged, down to a step parked between retry attempts that only its own timer job still names; the query count no longer grows with the listing, which the new engine test pins by reading H2's own query statistics. The single-instance path keeps its targeted queries. Also: the two in-memory engine tests meant DB_CLOSE_DELAY=-1, not 1000. Fixes #7250 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7193.
Root cause
Flowable's
JobRetryCmdcallsexecutionEntity.setActive(false)when it moves a failed asynchronous job to the timer-job table, andFindActiveActivityIdsCmdcollects only activity ids of executions whoseisActive()is true. So from the first failed attempt on, the execution is inactive and the pending job is the only remaining evidence of where the instance sits - whilegetProcessInstanceActiveActivityIdsqueried nothing but dead-letter jobs, which it maps tonegative.Because
BpmnIntentGeneratoremits its stepsflowable:async, an intent-generated process therefore answered "no step is running" for its whole life: nothing for the Processes viewer or the Monitoring diagram to overlay, and no way for an application to tell its own users which step a record's process is on.The change
Executable and timer jobs now count towards
positivealongside the active executions, merged by the higher of the two counts rather than by their sum. That matters:ContinueProcessOperationmarks the execution active before it creates the async job, and a boundary event's own child execution stays active beside its timer job - so summing would render every pending asynchronous step, and every boundary timer, as a "2" badge on both diagram overlays. Dead-letter jobs staynegative, exactly as before, and a job bound to no flow element (an asynchronous variable write, a process-instance migration) no longer becomes a literal"null"key in the answer.Two sites sharing the same root cause come with it:
getProcessInstanceImagehighlighted from the samegetActiveActivityIdscall, soGET /bpm-processes/diagram/instance/{id}had nothing to mark either.moveDeadLetterJobToExecutableJobhands the job back to the executor without reactivating the execution, so a click made both badges disappear and the step looked finished.And
ProcessInstanceData.activityId(the issue's second symptom,"activityId": nullonGET /bpm-processes/instances) is resolved the same way instead of read off the root process-instance execution, where Flowable always leaves it unset. The field is singular, so it carries the sole occupied activity and stays null for a fan-out, where/activeis the answer.Deliberately out of scope:
createSuspendedJobQuery()(suspension moves both job tables, but the platform exposes no suspend/activate path, so no instance is ever in that state) andgetProcessDefinitionActiveActivityIds, which queries executions without theisActivefilter and therefore already reports parked steps.One knowing limitation, noted in a code comment rather than passed over:
maxundercounts if a single element ever hosts both a waiting token and a parked one - an asynchronous task behind a loop or a fan-in. Counting exactly would take a per-activity execution query on an endpoint both UIs poll.Tests
BpmProviderFlowableActiveActivitiesTestdrives a real in-memory Flowable engine with the async executor off, so there are no threads and no sleeps:ManagementService#executeJobruns the attempt on the calling thread and Flowable's own failed-job listener then applies the retry decision in its own transaction. Five states: a pending job (guarding the double count), parked between retry attempts, dead-lettered, retried out of the dead letter, and a plain wait state.BpmActiveActivitiesITpublishes a throwing client delegate on aflowable:asyncstep with anR5/PT1Hcycle (load-bearing - without a cycle the executor dead-letters the job within half a minute and the state under test would expire mid-test), waits for the parked timer job, and asserts that job's exception message carries the delegate's own text, so the test cannot pass on a job that never resolved the compiled class. It then asserts both HTTP answers. Untagged, so it runs in the per-PR smoke gate; ~21 s.Both suites were confirmed to fail without the production change and pass with it. Also green locally: the module's full unit suite, and
JavaBpmnIT/BpmTaskLabelKeyIT/JavaDelegateInjectionIT/BpmnModelApiIT(the integration tests exercising the changed instance mapping).formatter:validateis clean and the-P releasejavadoc build reports no errors.🤖 Generated with Claude Code