Skip to content

Snapshot send-event task expressions at scheduling time for async jobs#4207

Closed
kle-dev wants to merge 1 commit intoflowable:mainfrom
kle-dev:fix/async-send-event-context-snapshot
Closed

Snapshot send-event task expressions at scheduling time for async jobs#4207
kle-dev wants to merge 1 commit intoflowable:mainfrom
kle-dev:fix/async-send-event-context-snapshot

Conversation

@kle-dev
Copy link
Copy Markdown

@kle-dev kle-dev commented May 4, 2026

Summary

Replaces #4205 with a fix at the root of the problem.

The async send-event job today re-evaluates eventInParameter and channelKey expressions in a worker thread, losing every thread-local backed context: ${authenticatedUserId} resolves to null, custom security beans return empty values, tenant-scoped helpers fall back to defaults. Behaviour diverges from sendSynchronously=true even though both paths end at the same dispatch. #4205 fixes the specific ${authenticatedUserId} symptom by capturing/restoring the authenticated user across the job boundary, but anything else context-dependent stays broken.

This PR resolves every expression in the scheduling thread — where variables, authenticated user, beans and other context are still available — and persists the result as a JSON snapshot on JobEntity.customValues. The async handler reads the snapshot, exposes it on the command context and lets SendEventTaskActivityBehavior dispatch the captured values verbatim. No expression is evaluated in the worker thread.

Async sending is now semantically identical to synchronous sending regardless of which thread-local context the eventInParameters depend on. Jobs scheduled by an older engine fall back to the legacy re-evaluation path so in-flight upgrades stay safe.

Changes

  • SendEventTaskActivityBehavior — async branch captures channel keys + payload values via writeSnapshot into customValues; sync branch consumes the snapshot exposed by the handler if present, otherwise resolves expressions as before. getChannelModels is split into resolveChannelKeys + resolveChannelModels for reuse.
  • AsyncSendEventJobHandler — reads customValues, parses the JSON snapshot and exposes it on the CommandContext under SNAPSHOT_ATTRIBUTE. The PR Propagate authenticated user when send-event task is sent asynchronously #4205 mechanism (jobHandlerConfiguration carrying the authenticated user) is no longer needed.
  • SendEventTaskTest — keeps testSendEventWithAuthenticatedUserExpression from Propagate authenticated user when send-event task is sent asynchronously #4205 as a regression case; adds testSendEventWithThreadLocalBeanExpression which registers a custom bean backed by a ThreadLocal, proving the fix is not specific to Authentication.

Notes on payload value round-trip

Payload values are persisted via the engine's ObjectMapper. Strings, numbers, booleans, JSON nodes and null round-trip losslessly. Custom POJOs / dates pass through Jackson's default serialization — the same path the existing JSON outbound serializer would use, so the observable channel output is unchanged for the common cases.

Test plan

  • mvn test -pl modules/flowable-engine -Dtest=SendEventTaskTest — 14/14 pass
  • Broader regression: mvn test -pl modules/flowable-engine -Dtest='*EventRegistry*,*SendEvent*,*ReceiveEvent*' — 81/81 pass

The async send-event job re-evaluates eventInParameter and channelKey
expressions in a worker thread, which loses every thread-local backed
context: ${authenticatedUserId} resolves to null, custom security beans
return empty values, tenant-scoped helpers fall back to defaults, and so
on. Behaviour diverges from sendSynchronously=true even though both
paths end at the same dispatch.

Resolve every expression in the scheduling thread (where the original
variables, authenticated user, beans and other context are still
available) and persist the result as a JSON snapshot on
JobEntity.customValues. The async handler reads the snapshot, exposes
it on the command context and lets the activity behaviour dispatch the
captured values verbatim — no expression is evaluated in the worker.

Async sending is now semantically identical to synchronous sending
regardless of which thread-local context the eventInParameters depend
on. Jobs scheduled by an older engine fall back to the legacy
re-evaluation path so in-flight upgrades stay safe.

Tests: testSendEventWithAuthenticatedUserExpression keeps the original
authenticated-user case; testSendEventWithThreadLocalBeanExpression
adds a custom bean backed by a ThreadLocal to demonstrate the fix is
not specific to Authentication.
@kle-dev kle-dev closed this May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant