Skip to content

feat: Publisher interface and adapter event types#7

Merged
klaidliadon merged 6 commits into
masterfrom
feat/publisher-events
May 13, 2026
Merged

feat: Publisher interface and adapter event types#7
klaidliadon merged 6 commits into
masterfrom
feat/publisher-events

Conversation

@klaidliadon
Copy link
Copy Markdown
Contributor

@klaidliadon klaidliadon commented May 12, 2026

Adds an observability layer for adapters. A runnable.Publisher interface (single Publish(any) method) is installed onto the runnable's ctx via runnable.WithPublisher; adapters call runnable.Publish(ctx, event) to emit typed events. StatusStore becomes a Publisher and Status.Restarts is back — counted from RetryEvent instead of the deprecated WithRetry onStart side-channel.

Stacked on #4 — review against feat/drain-and-ticker, not master.

Closes marino39's #2 quick-glance item (StatusStore refactor so adapters can store stuff).


Commits

  • 01eed91Publisher interface + Publishers fanout + WithPublisher Option + ctx helpers (PublisherFrom, Publish). Event types (RetryEvent, DrainStartedEvent, DrainTimedOutEvent, PanicRecoveredEvent).
  • 94d8068 — Wire publish calls into Retry / Draining / Recovering. Recovering drops its PanicHandler argument (single way to handle panics: subscribe via Publisher).
  • 8700487WithStatus registers a tagged statusPublisher; Status.Restarts increments from RetryEvent. Integration test (in runnable_test to avoid an import cycle) verifies WithStatus + adapters.Retry end-to-end.
  • 5616225 — README "Observability via Publisher" section + updated migration table.

Shape

type log struct{}

func (log) Publish(event any) {
    switch ev := event.(type) {
    case runnable.RetryEvent:
        slog.Warn("retry", "attempt", ev.Attempt, "err", ev.Err)
    case runnable.PanicRecoveredEvent:
        slog.Error("panic", "err", ev.Recovered)
    }
}

r := runnable.New(work,
    runnable.WithStatus("recon", store), // store sees RetryEvent → Restarts++
    runnable.WithPublisher(log{}),       // log sees the same events
    runnable.WithAdapters(
        adapters.Retry(3, time.Minute),
        adapters.Recovering(),
    ),
)

Test plan

  • go test -race -count=1 ./... — passes.
  • Vacuous-test check: commented out the r.publisher = mergePublisher(...) wiring in WithStatus.apply and confirmed TestStatusRestartsCountedFromRetryAdapter fails (expected 2, got 0) before restoring.
  • golangci-lint run ./... — clean.

Notes

  • Publisher signature is Publish(event any) — single method, no source/ID parameter. WithStatus tags events with the runnable ID via an internal statusPublisher wrapper.
  • Multi-publisher composition: WithPublisher (and WithStatus) stack additively via a Publishers fanout type. Order doesn't matter.
  • Synchronous dispatch: Publisher.Publish runs on the goroutine that emits. Documented in README. Subscribers needing async dispatch should buffer internally.
  • Breaking change carried in this stack: adapters.Recovering() no longer takes a PanicHandler. Migration is one extra Option (WithPublisher) — documented.

@klaidliadon klaidliadon changed the base branch from refactor/adapters-middleware to feat/drain-and-ticker May 12, 2026 21:26
@klaidliadon klaidliadon force-pushed the feat/publisher-events branch from 0e13e6b to 5616225 Compare May 12, 2026 21:27
@klaidliadon klaidliadon force-pushed the feat/publisher-events branch from 5616225 to 85acb90 Compare May 12, 2026 21:43
Base automatically changed from feat/drain-and-ticker to master May 13, 2026 13:54
@marino39
Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copy link
Copy Markdown
Contributor

@marino39 marino39 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - however you need to rebase

Introduce runnable.Publisher (single-method, takes any event), a
fanout Publishers type, ctx helpers (PublisherFrom, Publish), and the
WithPublisher Option that stacks additively across multiple calls.

Add event types (RetryEvent, DrainStartedEvent, DrainTimedOutEvent,
PanicRecoveredEvent) that adapters publish in later commits.

Run installs the configured publisher into runCtx so adapters can
reach it via runnable.Publish without explicit wiring.
Wire publish calls into the three event-emitting adapters:

- Retry publishes runnable.RetryEvent (1-indexed Attempt + Err) after
  each failed attempt that does not exhaust the budget. Context-error
  give-ups do not emit an event.
- Draining publishes runnable.DrainStartedEvent when the outer ctx is
  cancelled and runnable.DrainTimedOutEvent when the grace window
  expires.
- Recovering drops its PanicHandler argument; panics now surface
  exclusively via runnable.PanicRecoveredEvent on the ctx Publisher.

Update the ticker-with-drain example to install a logPublisher
implementing the Publisher interface and switch on event type.
WithStatus registers a per-id statusPublisher with the runnable, which
forwards events to StatusStore.publish. RetryEvent increments the
restarts counter for that id; other events are no-ops for now.

Re-introduces the Restarts field on Status that was dropped earlier
in this stack when WithRetry's onStart side-channel was severed. The
counter is now event-driven, so any Publisher (not just adapters.Retry)
that emits RetryEvent will be reflected.

Add an integration test in runnable_test exercising
WithStatus + adapters.Retry end-to-end (lives in runnable_test rather
than runnable to avoid an import cycle).
Add an Observability via Publisher section showing how to subscribe to
RetryEvent / DrainStartedEvent / PanicRecoveredEvent through a custom
Publisher implementation and runnable.WithPublisher.

Update the migration table for the Recovering signature change (no
PanicHandler argument; use a Publisher subscriber instead) and note
that Status.Restarts is now event-driven rather than removed.
Trim doc comments on Publisher / Publishers / PublisherFrom / Publish /
WithPublisher to one or two purpose-stating lines each.
The helper does not always append — it returns one operand unchanged
when the other is nil, and only fans out via Publishers when both are
present. merge captures the semantics more precisely.
@klaidliadon klaidliadon force-pushed the feat/publisher-events branch from d38c7cd to f34fb06 Compare May 13, 2026 14:02
@klaidliadon klaidliadon merged commit 7227e90 into master May 13, 2026
4 checks passed
@klaidliadon klaidliadon deleted the feat/publisher-events branch May 13, 2026 14:06
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.

2 participants