Skip to content

test(dispatcher): guard tenant-stream msg IDs against drift - #4547

Merged
igor-kupczynski merged 2 commits into
mainfrom
tenant-stream-msg-ids-drift-test
Jul 28, 2026
Merged

test(dispatcher): guard tenant-stream msg IDs against drift#4547
igor-kupczynski merged 2 commits into
mainfrom
tenant-stream-msg-ids-drift-test

Conversation

@igor-kupczynski

@igor-kupczynski igor-kupczynski commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to #4480, which turned the implicit durable-send mirror to tenant streams into an explicit publish allowlist (msgqueue.tenantStreamMsgIDs). During that review the task-cancelled API cancellation path nearly fell out of the allowlist silently — nothing kept it in sync with what the dispatcher's streams actually consume, in either direction (a consumed ID missing from the allowlist is never published; an allowlisted ID nobody consumes is wasted publishes).

  • Guard: TestTenantStreamMsgIDsInSync asserts set equality between msgqueue.TenantStreamMsgIDs() (new accessor) and the message IDs the dispatcher's streams consume, with failure messages saying exactly what to update on which side.
  • How: the dispatcher's stream switches became introspectable maps — workflowEventConverters and workflowRunMatchers — extracted together with msgsToWorkflowEvent / isMatchingWorkflowRunV1 (now receiver-less free functions) into tenant_stream_events.go, next to the test. The repeated decode/loop/append scaffolding is collapsed into two small generic adapters (eventConverter, runMatcher), so each map entry is just its payload type and field mapping.
  • Zero behavior change: logic is moved verbatim; map misses and empty match results return exactly what they did before. Net −11 lines (server.go −206).
flowchart LR
    P[producers<br/>PubTenantMessage] -->|gated by<br/>tenantStreamMsgIDs| S[tenant stream]
    S --> C[workflowEventConverters]
    S --> M[workflowRunMatchers]
    T[TestTenantStreamMsgIDsInSync] -. asserts allowlist == consumer keys .- C
    T -.- M
Loading

Type of change

  • New tests (plus a no-behavior-change refactor making the consumed ID set introspectable)

Checklist

Changes have been:

  • Tested (unit): go test ./internal/msgqueue/ ./internal/services/dispatcher/ passes; the drift test verified to fail with an actionable message when an ID is removed from either side
  • Linted and formatted

🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.

  • Details: Claude w/ Fable

The pub/sub split (#4480) made the implicit durable-send mirror to tenant
streams an explicit allowlist (msgqueue.tenantStreamMsgIDs), which nearly
dropped task-cancelled on the API cancellation path during review. Nothing
kept that allowlist in sync with what the dispatcher's streams consume.

Make the dispatcher the checkable source of truth: msgsToWorkflowEvent's
switch becomes a lookup in a package-level workflowEventConverters map and
isMatchingWorkflowRunV1's switch a lookup in workflowRunMatchers, preserving
behavior. msgqueue exports the allowlist via TenantStreamMsgIDs(), and
TestTenantStreamMsgIDsInSync asserts set equality between the two, with
failure messages saying exactly what to update on either side.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hatchet-docs Ready Ready Preview, Comment Jul 28, 2026 10:04am

Request Review

@github-actions github-actions Bot added the engine Related to the core Hatchet engine label Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

goos: linux
goarch: amd64
pkg: github.com/hatchet-dev/hatchet/internal/msgqueue
cpu: AMD Ryzen 9 7950X3D 16-Core Processor          
                              │ /tmp/old.txt │            /tmp/new.txt            │
                              │    sec/op    │    sec/op     vs base              │
CompressPayloads_1x10KiB-8      108.3µ ±  6%   110.6µ ±  8%       ~ (p=0.699 n=6)
CompressPayloads_10x10KiB-8     1.172m ± 12%   1.141m ±  6%       ~ (p=0.699 n=6)
CompressPayloads_10x100KiB-8    13.07m ±  6%   13.57m ±  4%       ~ (p=0.065 n=6)
CompressPayloads_Concurrent-8   76.03µ ± 15%   80.53µ ± 41%  +5.91% (p=0.026 n=6)
geomean                         596.0µ         609.3µ        +2.25%

                              │ /tmp/old.txt │            /tmp/new.txt            │
                              │     B/op     │     B/op      vs base              │
CompressPayloads_1x10KiB-8      10.98Ki ± 2%   11.02Ki ± 1%       ~ (p=0.506 n=6)
CompressPayloads_10x10KiB-8     108.5Ki ± 1%   108.4Ki ± 1%       ~ (p=0.310 n=6)
CompressPayloads_10x100KiB-8    2.921Mi ± 1%   2.921Mi ± 0%       ~ (p=0.699 n=6)
CompressPayloads_Concurrent-8   54.24Ki ± 0%   54.43Ki ± 1%       ~ (p=0.180 n=6)
geomean                         117.9Ki        118.1Ki       +0.16%

                              │ /tmp/old.txt │            /tmp/new.txt            │
                              │  allocs/op   │ allocs/op   vs base                │
CompressPayloads_1x10KiB-8        5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=6) ¹
CompressPayloads_10x10KiB-8       32.00 ± 0%   32.00 ± 0%       ~ (p=1.000 n=6) ¹
CompressPayloads_10x100KiB-8      63.00 ± 2%   63.00 ± 0%       ~ (p=1.000 n=6)
CompressPayloads_Concurrent-8     17.00 ± 0%   17.00 ± 0%       ~ (p=1.000 n=6) ¹
geomean                           20.35        20.35       +0.00%
¹ all samples are equal

Compared against main (969d558)

…wn file

Move workflowEventConverters, workflowRunMatchers, msgsToWorkflowEvent and
isMatchingWorkflowRunV1 out of server.go into tenant_stream_events.go, next
to the drift test that asserts their keys against msgqueue's allowlist.

Collapse the repeated decode/loop/append scaffolding into two small generic
adapters (eventConverter, runMatcher), so each map entry is just its payload
type and field mapping; the workflow-run-finished status switch stays inside
its closure. Drop the unused DispatcherImpl receivers from both functions.

No behavior change: map misses and empty match results return exactly what
they did before.
@igor-kupczynski
igor-kupczynski merged commit 5bd467c into main Jul 28, 2026
61 checks passed
@igor-kupczynski
igor-kupczynski deleted the tenant-stream-msg-ids-drift-test branch July 28, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Related to the core Hatchet engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants