Skip to content

Automatic (half-open) recovery for disabled log destinations #24

Description

@anth-volk

Context

#22 / PR #23 add bounded, asynchronous log emission. The failure handling is deliberately fail-open and one-way: after DESTINATION_FAILURE_LIMIT (3) consecutive failures, a destination is disabled — the async wrapper trips, the manager removes the destination and falls back to stdout — and it stays disabled until something calls restart_observability(), which rebuilds all destinations from config with fresh clients.

That manual-recovery design is the right first step (predictable, no thundering probes during an outage), but it means a transient Cloud Logging incident permanently downgrades a long-lived process to stdout-only until a deploy, snapshot restore, or explicit restart call.

Proposal

Add an automatic half-open recovery path on top of the existing breaker:

  • After a destination is disabled, start a cooldown (e.g. 60s, exponential up to a cap).
  • When the cooldown elapses, the next emit (or a lightweight timer) sends a single probe record through a freshly built destination (half-open state).
  • Probe succeeds → destination is reinstated and the cooldown resets. Probe fails → destination stays disabled and the cooldown doubles.

Design constraints carried over from #22:

  • Fail-open above all: probing must never add latency or exceptions to the caller's path (probe from the background worker or a timer thread, never the request thread).
  • Rebuild, don't poke: reinstatement should construct a fresh destination (fresh client/transport) exactly like restart() does, not resurrect the tripped instance — tripped BackgroundEmitDestination wrappers are terminal by design.
  • One probe at a time per destination (no thundering herd from concurrent emits observing an elapsed cooldown).
  • Telemetry: report state transitions (half_open_probe, destination_reinstated) through the same internal-error channel as destination_disabled.

Why not in #23

The breaker + manual restart_observability() covers the immediate consumers: Modal workers call restart in their post-snapshot hook anyway, and Cloud Run instances are short-lived enough that a disabled destination rarely outlives the incident. Automatic recovery adds a state machine (closed/open/half-open, cooldown clock, probe serialization) that deserves its own review.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions