-
Fix (fork/telemetry): a forked child now reports under its OWN
instanceHash(qfg-xcym).@instance_hashwas minted once inClient#initializeand survivedfork(2), so every forked child POSTed its telemetry under the parent's hash. The Quonfig Debugger groups SDK last-seen by that hash, so an 8-worker Puma cluster (or aparallel-gem batch) collapsed into a single instance row with the parent's and the children's windows interleaved on top of each other. A child now mints a fresh hash as part of the same post-fork rebuild that gives it fresh aggregators and a fresh reporter — before the reporter is constructed, so the hash the child POSTs under is its own. This is parity with Reforge, where a forked process simply builds a whole new client. The parent's hash is never touched, and a child's row count is unchanged (it already reported its own window; it just did so under the wrong identity). Present since 0.0.16. -
Fix (telemetry): only a STRICTLY older payload counts as
guardRejected(qfg-rr5b). The reject-older install guard treated an equal-generation payload as "older" and counted it, but two server behaviors re-deliver the envelope a client already holds in perfectly normal steady state: api-delivery's SSEsendInitialConfigre-sends the current envelope on every connect regardless of theLast-Event-Idthe client sends, and a config poll against an empty per-leg ETag slot — a fresh transport, a reconnect, the fallback poller's engage fetch — answers with a full 200 at the same generation. A healthy client therefore reportedguardRejected >= 1from initialization alone and picked up another on every SSE reconnect, which is noise in a field whose entire purpose is to say "a leg tried to move us backwards". Semantic narrowing worth noting if you query this field: equal-generation re-deliveries are no longer counted, soguardRejectedwill read lower than on 1.4.0 for the same traffic — that is the fix, not a regression. Nothing else about the guard changed: an equal-generation envelope is still not installed, still returns:not_modified, and still advances the liveness stamp exactly where it did before, and the unversioned (generation <= 0) carve-out is untouched. No wire, ClickHouse, or dashboard-query change is required. This is the Ruby half of a change being applied across all six backend SDKs (sdk-python ships it in 1.4.1; Go, Java, .NET and Node follow in their next releases), so counts may differ across languages until then. -
Docs (fork): a child forked from inside an
on_updatecallback must exit (qfg-ec2q).on_updateruns on the SDK's SSE reader thread, so a child created with non-blockfork(no block) from inside that callback inherits that thread as its one surviving thread. If the child does its work and returns from the callback instead of exiting, the inherited reader resumes its read loop on the parent's socket and consumes frames the parent never sees. The README fork section now says toexit!(orexec) from such a child. Block-formfork { ... }, any child that exits, the documented patterns, and theparallelgem are all unaffected — no code change. -
Include version in
getrequest [#165]