Skip to content

pool: tell a subscription when it may have missed events (macula-station#129) - #64

Merged
rgfaber merged 3 commits into
masterfrom
mercurius/129-delivery-gaps
Oct 30, 2026
Merged

rgfaber merged 3 commits into
masterfrom
mercurius/129-delivery-gaps

Conversation

@rgfaber

@rgfaber rgfaber commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Purpose (reliability): an application reading a subscription is told, in-band, when the connection to its stations dropped long enough for events to be lost, instead of reading on as if nothing happened.

Refs macula-io/macula-station#129 (phase 1 spec: https://github.com/macula-io/macula-station/issues/129#issuecomment-6527386000). Stacked on #63 (macula-go#62), whose 300 s teststation the live tests rely on; it retargets to master once #63 merges.

What changes

  • Subscription.Events() delivers pool.Delivery (breaking, next minor): KindEvent (Delivery.Event), KindInterrupted once when the last link carrying the subscription ends (FromMs = that link's last heard, Reason = its end reason), KindGap once when a station accepts it again, before any event of that link (FromMs..ToMs).
  • Coverage bookkeeping (pool/delivery.go): a link covers a subscription from its accepted REGISTER_ACK until the link ends. Nothing while another link covers it, for an outage the link survives, at the first subscribe, or after Unsubscribe/Close. A refused re-subscribe leaves it interrupted with no Gap.
  • A notice is never dropped for a full subscription: the oldest delivery makes room (an evicted event is counted in Dropped). The C ABI inbox follows the same rule, and every item gets "kind": event, interrupted (from_ms, reason), gap (from_ms, to_ms, reason).
  • stationlink.Link.LastHeard, stationlink.EndReason (liveness_lost, closed, goodbye, status_expired, binding_expired, malformed_frame, idle_timeout, closed_by_station, connection_lost).
  • teststation.Station.Silence: a path outage QUIC keeps the connection through; what falls in it is lost.
  • README section "When a subscription may have missed events", CHANGELOG, CONTRACT.md, macula.h.

Tests (the spec's shape, against the teststation)

  1. TestAnOutagePastLivenessDeliversAnInterruptedThenAGap: 90 s silence, one Interrupted (liveness_lost, from <= silence start + 1 s), then one Gap before any event of the new link, while events are published every 20 ms across the ack (~91 s).
  2. TestOneOfTwoLinksEndingDeliversNoNotice
  3. TestBothLinksEndingAndOneReturningDeliversOneOfEach
  4. TestAShortOutageTheLinkSurvivesDeliversNoNotice: 10 s silence, 45 s watched, same link after (~47 s).
  5. TestARefusedResubscribeDeliversNoGap
  6. TestUnsubscribingWhileInterruptedDeliversNothingMore
    (7 is Erlang-only.) Unit tests cover the coverage set (empty, refilled, simultaneous ends, a link that ends before its ack), EndReason, LastHeard, and the C ABI notice JSON and full-inbox rule. Cases 1 and 4 run in parallel.

🤖 Generated with Claude Code

rgfaber and others added 2 commits October 30, 2026 17:25
…a-station#129)

When the last link carrying a subscription ended, the pool redialed and
subscribed again in silence, so whatever the station sent to the ended link
was gone without a word (craft-collab#38). Events() now delivers
pool.Delivery: an event, or KindInterrupted once when the last covering link
ends (from its last heard, for its end reason) and KindGap once when a
station accepts the subscription again, before any event of that link.
Nothing while another link covers it, for an outage the link survives, or at
the first subscribe; a notice is never dropped for a full subscription.

stationlink gains Link.LastHeard and EndReason; the C ABI inbox gives every
item a "kind"; teststation gains Silence, a path outage the connection
survives, which the live tests use (phase 1 spec, issuecomment-6527386000).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…129)

Close cancels the pool's context, then ends its links; each link's end read
as the last covering link ending and delivered an Interrupted before the
subscription closed, which the C ABI test caught. A subscription whose
context ended owes no notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rgfaber
rgfaber changed the base branch from mercurius/62-teststation-idle to master October 30, 2026 21:05
open() handed a notice back to notify, which for a group subscription
sends to opening again, so open read it forever: no Interrupted or Gap
ever reached Events and open spun. open now pushes a notice straight to
Events; notify keeps choosing the next stage for attach and forward.

A link accepted before an ended link's forward recorded its end found
the set still covered and owed nothing; attach now records every
covering link that already ended first.

README: a Gap can push out an unread Interrupted on a full buffer, and
a group's notice waits behind events still being opened.

Refs macula-io/macula-station#129

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rgfaber
rgfaber merged commit fae0d78 into master Oct 30, 2026
14 checks passed
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