Skip to content

CxODEV-2125: keep the Kafka cancellation consumer running after a bad message (4.4.2) - #229

Closed
milansvitlica-codaxy wants to merge 1 commit into
masterfrom
CxODEV-2125-kafka-notifier-skip-bad-messages
Closed

milansvitlica-codaxy wants to merge 1 commit into
masterfrom
CxODEV-2125-kafka-notifier-skip-bad-messages

Conversation

@milansvitlica-codaxy

@milansvitlica-codaxy milansvitlica-codaxy commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

KafkaConsumerBackgroundService runs its consume loop inside one try/catch. When a single message fails, the catch logs "will retry to consume after 5 seconds", waits, and ExecuteAsync returns. Nothing restarts the consumer. The worker keeps running, but it stops cancelling tasks until the process restarts.

Two kinds of message trigger this:

  • The handler throws. A status event with "workflowTask": null makes HandleKafkaEvent throw a NullReferenceException on WorkflowTask.Type.
  • The value fails to deserialize. Examples: invalid or empty JSON, an unknown status, or a workflowTask without name or taskReferenceName (both are Required.Always on the generated model). Consume then throws a ConsumeException.

Fix

  • Catch errors per message, inside the loop. Log the error and move on to the next message.
  • OperationCanceledException from the stopping token still ends the loop, so shutdown is unchanged.
  • Drop the 5-second pause. It only ran once, right before the loop exited. Pausing on every bad message would delay every cancellation queued behind it.
  • Add InternalsVisibleTo("ConductorSharp.Engine.Tests") to the notifier assembly, as ConductorSharp.Engine already does.
  • Bump all packages 4.4.1 → 4.4.2.

Tests

New KafkaCancellationNotifierTests run the real consumer and notifier against librdkafka's in-process mock cluster (test.mock.num.brokers). No broker or Docker is needed.

Test Before the fix After the fix
CancelsTaskAfterUnprocessableMessage: "workflowTask": null, then a CANCELED event ❌ task not cancelled (20 s timeout) ✅ ~0.5 s
CancelsTaskAfterUnprocessableMessage: not json, then a CANCELED event ❌ task not cancelled (20 s timeout) ✅ ~0.5 s
StopAsyncEndsConsumption ✅ ✅

StopAsyncEndsConsumption guards shutdown. It fails if the per-message catch also swallows OperationCanceledException; I checked this by applying that change.

Full suite, dotnet test ConductorSharp.sln: 76 passed, 0 failed. Run locally on the .NET 8 runtime (DOTNET_ROLL_FORWARD=Major); CI runs it on .NET 6.

Note for local runs on Windows with core.autocrlf=true: 30 JSON fixture tests already fail on master there (CRLF fixtures vs LF output). The run above used an LF checkout, like CI.

Backport to v3: #230 (3.10.1).

🤖 Generated with Claude Code

The consume loop sat inside a single try/catch. When one message failed,
the catch logged "will retry to consume after 5 seconds", waited, and then
ExecuteAsync returned. The consumer never restarted, so the worker kept
running but stopped cancelling tasks until the process restarted.

Two kinds of message trigger it:
- the handler throws, e.g. a status event with "workflowTask": null
  (NullReferenceException on WorkflowTask.Type);
- the value fails to deserialize (invalid or empty JSON, an unknown
  status, a workflowTask without name or taskReferenceName), so Consume
  throws a ConsumeException.

Errors are now caught per message: logged, and the loop moves on to the
next message. OperationCanceledException from the stopping token still
ends the loop, so shutdown is unchanged. The 5-second pause is gone. It
only ran once before the loop exited, and pausing on every bad message
would hold up every cancellation behind it.

Tests run the real consumer against librdkafka's in-process mock cluster
(test.mock.num.brokers), so they need no broker or Docker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milansvitlica-codaxy

Copy link
Copy Markdown
Collaborator Author

Closing without merging: not pursuing this change for now.

@milansvitlica-codaxy
milansvitlica-codaxy deleted the CxODEV-2125-kafka-notifier-skip-bad-messages branch October 9, 2026 08:41
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