Postboy Help

Middleware Troubleshooting

Summary

This page helps you diagnose common middleware issues:

  • middleware not running,

  • running too often,

  • cancelling operations unexpectedly,

  • missing after hooks,

  • ordering problems,

  • leaks and test pollution.

It follows a “symptom → likely causes → fixes → prevention” structure.

Quick triage checklist (60 seconds)

Before deep debugging, answer these questions:

  1. Which stage is affected? Publish, Callback, or Execute.

  2. Is the operation cancelled? Look for a cancellation error and its metadata (stage, middleware name, message id).

  3. Does canHandle match? If it returns false, the middleware is skipped.

  4. Is middleware attached to the same bus instance? Multiple buses are a common cause.

  5. Does ordering matter? The first Interrupt wins; later middleware won’t run.

Symptom 1: “My middleware never runs”

Likely causes (ranked)

  1. Middleware is attached to a different Postboy instance than the one used to publish/execute.

  2. canHandle returns false for the tested stage/message.

  3. Middleware was attached too late (after operations already happened).

  4. You are testing a different stage than you think (Publish vs Callback vs Execute).

Fix steps

  1. Confirm the exact bus instance used by the caller and the middleware attachment code.

  2. Temporarily make canHandle return true and verify before executes.

  3. Add a temporary log in before that prints:

  • stage

  • message label/type

  • message id

  1. Attach middleware at a stable startup point (before message processing begins).

Prevention

  • Centralize bus creation and middleware attachment in one place.

  • Keep stage-first filtering and log stage during initial rollout.

Symptom 2: “Middleware runs for everything and creates noise”

Likely causes

  1. canHandle is too broad (always true).

  2. Stage filtering is missing.

  3. Message filtering is missing or implemented inside before instead of canHandle.

Fix steps

  1. Move all scoping into canHandle.

  2. Filter by stage first, then by message subset.

  3. If this is a debug middleware, add an explicit feature flag to disable it.

Prevention

  • Adopt a “default deny” mindset for observability middleware: opt into specific stages/types.

Symptom 3: “Operations are cancelled unexpectedly”

Likely causes (ranked)

  1. A guard middleware returns Interrupt for more cases than intended.

  2. Middleware ordering changed; a different middleware now interrupts first.

  3. A canHandle predicate matches unintended messages/stages.

  4. Guard decisions depend on mutable global state (feature flags/config) that changes during runtime.

Fix steps

  1. Check the cancellation error metadata:

  • which middleware cancelled

  • stage

  • message id

  1. Temporarily add “decision logs” to guard middleware:

  • stage

  • message label

  • decision (Continue/Interrupt)

  • policy reason / policy id

  1. Narrow canHandle:

  • stage-first

  • explicit message subset

  1. Verify middleware ordering and document precedence.

Prevention

  • Use one-policy-per-middleware.

  • Keep cancellation reasons stable and searchable.

  • Treat guard middleware as part of your “public behavior” and test it.

Symptom 4: “My after() is not called”

Most common cause

The operation did not reach the successful post phase:

  • it was cancelled (Interrupt in before), or

  • it failed before reaching the post hook boundary.

Fix steps

  1. If you need to observe cancellations, log/measure in before (attempt logs).

  2. Do not rely on after for critical cleanup.

  3. For timing, use bounded state so missing after does not leak memory.

Prevention

  • Design middleware with the assumption “after may not run”.

  • Put cleanup for long-lived resources in dispose.

Symptom 5: “My logging/metrics doesn’t show cancelled attempts”

Likely causes

  1. You only log/record in after.

  2. Observability middleware is placed after the guard middleware in the chain.

Fix steps

  1. Log “attempt” in before.

  2. If you want to log cancellations, place logging middleware before guards (ordering).

  3. Alternatively, instrument cancellation where it is handled at the call boundary.

Prevention

  • Decide explicitly whether you want:

    • attempts (including cancelled), or

    • only successful completions, and position middleware accordingly.

Symptom 6: “Changing middleware order changes behavior”

Likely causes

  1. Hidden ordering dependencies.

  2. Multiple guards overlap; precedence was accidental.

Fix steps

  1. List the middleware chain in order and state its intent.

  2. Identify which middleware can Interrupt.

  3. Ensure the most specific policy runs first (or document your precedence rule).

Prevention

  • Avoid coupling between middleware instances.

  • Document order-sensitive chains in one module and treat it as configuration.

Symptom 7: “Performance regressed after adding middleware”

Likely causes (ranked)

  1. Overbroad canHandle causing hot-path overhead.

  2. Heavy work in before (serialization, logging payloads).

  3. Too many metrics labels or dynamic label building.

  4. Synchronous logging/export on hot paths.

Fix steps

  1. Filter by stage and message subset (tighten canHandle).

  2. Remove payload logging; log only stable identifiers.

  3. Precompute stable labels.

  4. Measure overhead by comparing with and without middleware in a controlled benchmark.

Prevention

  • Treat middleware as part of the performance-critical path.

  • Keep before lightweight and deterministic.

Symptom 8: “Memory grows over time” (suspected leak)

Likely causes

  1. Unbounded per-operation state (e.g., Map keyed by message id) cleared only in after.

  2. Subscriptions created by middleware are never unsubscribed.

  3. Timers/intervals never cleared.

  4. Middleware attached repeatedly (multiple times) without removal/disposal.

Fix steps

  1. Audit resources owned by middleware:

  • subscriptions

  • timers

  • maps/caches

  1. Ensure all long-lived cleanup is in dispose.

  2. Make per-operation maps bounded (TTL/LRU) and avoid relying on after.

  3. Ensure middleware is attached once per intended lifecycle scope.

Prevention

  • Add automated tests that:

    • attach middleware,

    • run operations,

    • dispose middleware/bus,

    • assert resources are released.

Symptom 9: “Tests affect each other” (cross-test pollution)

Likely causes

  1. Global bus instance reused without reset.

  2. Middleware attached in one test and not removed/disposed.

  3. Timers/subscriptions keep running after test completion.

Fix steps

  1. Attach middleware per test and remove/dispose in teardown.

  2. Dispose the bus (or a test harness that disposes it) after each test.

  3. Ensure dispose clears intervals and unsubscribes.

Prevention

  • Adopt a strict Arrange/Act/Assert + teardown pattern.

  • Prefer test-owned middleware scope unless you intentionally want global instrumentation.

Diagnostic flow diagram (how to localize the issue)

@startuml title Middleware debugging flow (localization)

start :Identify stage (Publish/Callback/Execute); :Check if operation was cancelled; if (Cancelled?) then (yes) :Inspect cancellation metadata\n(middleware name, reason, message id); :Check guard middleware canHandle conditions; :Verify middleware ordering; else (no) :Check whether middleware runs at all; if (Middleware runs?) then (yes) :Check canHandle scoping (too broad/too narrow); :Check before vs after expectations; else (no) :Verify middleware attachment to the correct bus; :Temporarily widen canHandle to true; :Attach earlier in lifecycle; endif endif stop

@enduml

Prevention checklist (keep this near your middleware chain)

  • canHandle is stage-first and cheap.

  • Guards have stable names and reasons.

  • “Attempt vs success” signals are intentional.

  • No cleanup depends on after.

  • dispose releases all long-lived resources.

  • Middleware ordering is documented if it impacts behavior.

  • Tests attach and dispose middleware predictably.

Next steps

  • [Middleware lifecycle and contract]

  • [Decision & Cancellation reference]

  • [Best practices]

  • [Anti-patterns]

  • [Recipes]

29 июня 2026