Skip to content

v0.0.16

Choose a tag to compare

@jdwyah jdwyah released this 15 May 19:19
· 85 commits to main since this release
  • Feat (SSE): replace ld-eventsource with an SDK-owned reconnect loop (qfg-35sm). sdk-ruby was the outlier among the four backend SDKs — sdk-go, sdk-node, and sdk-python all own their reconnect loop, only sdk-ruby handed it off to a library and scraped its log output to observe reconnects. The wire format we actually consume (plain JSON envelopes in single-line data: frames, no named events, no retry directives) is trivial enough that an SDK-owned loop is clearer than the library wrapper. New Quonfig::SSEConfigClient (~520 LoC, lib/quonfig/sse_config_client.rb) handles connect/parse/reconnect end-to-end. restart_total is now incremented at exactly one site under a mutex — verifiable, not log-scraped. ld-eventsource and the transitive http gem are removed from the gemspec. ReconnectCountingLogger and the sse_reconnect_reset_interval option (both 0.0.15-era defensive scaffolding around upstream behavior) are deleted — the bugs they defended against don't exist when the SDK owns the loop. Chaos: 10/10 in a 36-min run (scenarios 02 silent-stall, 05 sse-down-fallback, 09 flapping kill-storm).
  • Fix (SSE): contain watchdog Thread#raise with Thread.handle_interrupt (qfg-tj18). The new watchdog fires Thread#raise(SSEReadDeadlineExceeded) into the worker on a silent stall — the only reliable cross-platform way to unblock a Ruby thread blocked in Net::HTTP's body-read on macOS. The decision to fire is mutex-guarded against stop(), but the raise itself is delivered at the worker's next interrupt checkpoint, which could be anywhere in the call stack. run_loop's body now runs under Thread.handle_interrupt(SSEReadDeadlineExceeded => :on_blocking) so a late-landing raise can only land inside a blocking call; the read_body block explicitly switches to :immediate. A paranoid backstop rescue outside the until-@stopped loop ensures an escaped raise can never silently kill the worker.
  • Fix (SSE): isolate on_envelope callback exceptions (qfg-m3lk). A buggy user-supplied listener that raised during envelope delivery used to propagate out of read_body, get caught by run_loop as a transport error, bump restart_total, and reconnect — a perpetual reconnect storm at api-delivery-sse driven by a customer code bug. The callback is now wrapped in begin/rescue StandardError at the invocation site; exceptions are logged with class + message + backtrace sample and the stream continues uninterrupted. Interrupt and SystemExit are deliberately not caught so Ctrl-C still works.
  • Fix (SSE): classify 401/403/404 as terminal errors (qfg-i5xv). Non-200 responses used to be treated identically — SSEHTTPStatusError raised, run_loop rescued, restart_total bumped, backoff, retry, forever. For a bad SDK key (401) or revoked workspace (403) that was wasted load on api-delivery-sse with no recovery path short of a customer redeploy. New SSEHTTPTerminalError sentinel for 401/403/404; run_loop catches it, invokes on_error, exits the loop without bumping restart_total. Parent Quonfig::Client surfaces a terminal :sse_terminal_failure state distinct from transient :error. 429 and 5xx still retry.
  • Feat (fork): install Process._fork hook so SSE auto-restarts after fork (qfg-ryov). Ruby threads do not survive fork(2). Customers initializing Quonfig::Client in the Puma master (the preload_app! true / Rails config.eager_load = true convention) used to silently lose SSE in every worker child. New Quonfig::ForkSafety module prepends Process._fork and fans out across all live Quonfig::Client instances (tracked in an ObjectSpace::WeakMap): in the parent before the syscall, threaded components (SSE worker, polling supervisor, telemetry reporter) are torn down; in the child after the syscall, they are rebuilt. @stopped is preserved so a stop()-ed client stays stopped across fork. Covers Process.fork / Kernel#fork; Process.spawn and system("...") exec a new program so in-process state doesn't apply. Ruby 3.0 lacks Process._fork and is documented as requiring manual before_fork / on_worker_boot wiring.