Skip to content

Extract MessageClearer and add a positions-only DeleteMessages overload#173

Merged
stidsborg merged 5 commits into
mainfrom
message-clearer-and-positions-delete
Jun 20, 2026
Merged

Extract MessageClearer and add a positions-only DeleteMessages overload#173
stidsborg merged 5 commits into
mainfrom
message-clearer-and-positions-delete

Conversation

@stidsborg

Copy link
Copy Markdown
Owner

Summary

Two cohesive changes: a new position-only message delete, and the extraction of the handled-message lifecycle out of MessageWatchdog into a dedicated MessageClearer.

1. Positions-only DeleteMessages

IMessageStore.DeleteMessages(IEnumerable<long> positions) deletes messages by position regardless of flow. Positions are globally unique identity/auto-increment PKs, so no StoredId is needed — letting handled messages across many flows be removed in a single query. Implemented inline in every store (InMemory, PostgreSQL, MariaDB, SqlServer); the existing per-StoredId overload is untouched.

2. MessageClearer extraction

MessageWatchdog previously owned both the poll/push delivery loop and a coalescing delete pipeline + the _pushedPositions ignore-set. That second responsibility now lives in MessageClearer:

  • Ignore-setMarkPushed(positions) / NonClearedPositions() (the not-yet-cleared positions the watchdog passes to GetMessagesForReplica).
  • Coalescing, retry-until-success delete pipelineClear(positions): the first caller starts a single drain; calls arriving mid-flight batch up and flush together (one query per burst). A failed delete is retried until it lands (callers are told a message is gone only once it truly is), notifying the unhandled-exception handler each failure.

MessageWatchdog is now a pure poll/push loop with no shared mutable state. QueueManager depends on the narrow IMessageClearer instead of IMessageWatchdog and calls Clear(positions) — the previously-dead storedId argument is gone. FunctionsRegistry constructs one MessageClearer and shares it between the watchdog and the invocation path.

Tests

  • MessageClearerTests — coalescing, retry-on-failure (+ handler notified), concurrent load, and ignore-set trimming, all against a bare MessageClearer (just IMessageStore + handler + retry delay — no watchdog plumbing).
  • Hand-rolled QueueManager tests use a one-line IMessageClearer stub.

Verification

  • Full solution builds clean (0 warnings / 0 errors).
  • Full in-memory suite passes (522 tests). DB-store paths for the new overload build but are not yet exercised by tests (need Docker).

Add IMessageStore.DeleteMessages(IEnumerable<long> positions) - a position-only
delete (positions are globally unique identity values) so handled messages
across many flows can be removed in one query. Implemented inline in every store
(InMemory, PostgreSQL, MariaDB, SqlServer); the existing per-StoredId overload is
untouched.

Extract the handled-message lifecycle out of MessageWatchdog into a dedicated
MessageClearer: it owns the not-yet-cleared ignore-set (MarkPushed /
NonClearedPositions) and the coalescing, retry-until-success delete pipeline
(Clear). The watchdog becomes a pure poll/push loop with no shared mutable state.
QueueManager now depends on the narrow IMessageClearer instead of IMessageWatchdog
and calls Clear(positions) (the dead storedId argument is gone). A failed delete
is retried until it lands - callers are told a message is gone only once it truly
is - notifying the unhandled-exception handler on each failure.

Tests: MessageClearerTests covers coalescing, retry-on-failure, concurrent load,
and ignore-set trimming against a bare MessageClearer; the hand-rolled
QueueManager tests use a one-line IMessageClearer stub.
Add an end-to-end test against the real InMemoryFunctionStore: append messages,
await Clear for a subset, then assert GetMessages no longer returns them the
instant the task completes. Also gives the new positions-only DeleteMessages its
first real-store coverage.
Replace the hand-expanded @position{i} IN clause with the codebase's
STRING_SPLIT idiom (same shape as SetReplica), passing the positions as one
comma-joined @positions parameter.
Replace the hand-expanded IN (?, ?, ...) clause with FIND_IN_SET(position, ?),
matching the existing GetMessagesForReplica idiom - one comma-joined parameter
instead of one placeholder per position.
Callers already hold a List<long> (MessageClearer) and the type mirrors
IMessageClearer.Clear, so IReadOnlyList flows through without conversions. Drop
the now-redundant .ToList() materializations in the implementations.
@stidsborg stidsborg merged commit a87ac7a into main Jun 20, 2026
8 checks passed
@stidsborg stidsborg deleted the message-clearer-and-positions-delete branch June 20, 2026 09:18
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