Skip to content

Releases: canopy-labs/featureflip-ruby

v2.9.0

Choose a tag to compare

@jhoward321 jhoward321 released this 18 Sep 20:33

Fixed

  • The polling fallback no longer ends the SSE stream. After max_stream_retries (5) consecutive failures — about 31 seconds of unreachability, so an edge incident, a bad deploy or a network partition — the streaming thread called on_give_up and exited, and the core cleared its handler, so nothing could ever restart streaming: the process lost real-time updates until it was restarted (or Featureflip.restart was called by hand for fork-safety) and polled /v1/sdk/flags every 30 seconds indefinitely. Flag changes, kill switches included, then arrived up to a poll interval late. Polling is now additive — it covers the outage while the stream keeps retrying underneath at the capped, jittered backoff, and the next delivered frame retires the poller. The poller is retired from the frame-delivery path rather than when the stream next drops, because a healthy stream blocks in its read loop for its whole lifetime and a poller left running beside it reverts SSE deltas with its own whole-store replaces. (#3071)

v2.8.0

Choose a tag to compare

@jhoward321 jhoward321 released this 08 Sep 02:21

Fixed

  • A condition operator is now recognised however it is spelled: NotEquals, notequals, NOTEQUALS and not_equals all resolve to the same operator. This SDK previously matched the canonical PascalCase label exactly and treated every other spelling as unrecognised — and an unrecognised operator fails closed (#2262), so a mis-spelled operator matched nobody rather than erroring. The go and php SDKs each applied a different rule: go folded case but kept underscores, so it resolved notequals and rejected not_equals, while php inserted underscores ahead of PascalCase runs and did exactly the reverse. Each accepted a form the other refused, so one saved rule could serve different variations to two users purely by which SDK their service ran. All four now normalise by removing underscores and folding case — the one rule that is a superset of all four previous ones, so no SDK gets stricter and no configuration that evaluated before stops doing so. (#2374)

  • The operator is resolved once per condition and every subsequent lookup keys off that value, not just the dispatch arm. The operator name also selects the case-sensitivity set and the numeric-coercion set, so normalising at the dispatch alone would have made a mis-cased MatchesRegex match case-insensitively where its canonical spelling does not, and dropped a mis-cased NotEquals onto the string path, comparing "1" against "1.0" lexically. (#2374)

  • The fail-closed guarantee is unchanged and re-asserted: this resolves spellings, it does not invent operators. A name that is not an operator is still unrecognised and still matches nothing before negate can invert it. (#2262)

v2.7.0

Choose a tag to compare

@jhoward321 jhoward321 released this 27 Aug 18:20

Changed

  • A Before/After date operand that matches the ISO grammar but names no real calendar day now matches nothing, where it previously rolled over into the following month. 2024-02-30 resolved to 2024-03-01, 2023-02-29 (2023 is not a leap year) to 2023-03-01 and 2024-04-31 to 2024-05-01 — so a rule evaluated against a date its author never wrote. The evaluation engine and the C#, Go, Python and Java SDKs have always rejected these, so this converges the SDKs rather than making this one an outlier; until now a single saved rule could serve different variations to two users purely by which SDK their service ran. Follows #2480, which pinned the date grammar — an unreal day is inside that grammar, because a character class cannot express "is a real day", so the grammar guard was silent on it. (#2491)

  • The leap-year rule is applied in full, including the century exception: 1900-02-29 and 2100-02-29 match nothing (divisible by 100 but not 400), while 2000-02-29 and 2024-02-29 continue to match. The check runs on the operand's written date, before any offset is applied, so 2024-02-30T00:00:00+05:00 is rejected even though it would resolve to 2024-02-29T19:00Z — a date that does exist. (#2491)

If you have a targeting rule using one of these operands, rewrite it as the date you meant. The Management API has rejected an unreal day on write since #2480 (PortableDateOperand round-trips every grammar-matched operand through the engine's parser), so a rule saved from that release onward cannot carry one — only rules saved earlier are affected.

Fixed

  • An explicit client.flush no longer opens a second drain loop while one is already running. The in-flight latch added for #2456 guarded only the batch-size trigger, so the periodic flush, an explicit client.flush and a size-triggered flush could enter the loop together — two request streams against an endpoint the backoff gate exists to protect, and worse, a success in one cleared the gate a failure in the other had just armed, re-opening the one-request-per-evaluation behaviour outright. A caller arriving while a drain is running now waits for it and returns, matching the js and node SDKs. Shutdown still bypasses coalescing, because it is the last drain there will ever be. (#2477)

  • A Before/After date operand that resolves outside the representable date range now matches nothing, where it previously resolved to a real instant. The evaluation engine parses with DateTimeOffset.TryParse, so its accepted range is 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999Z and it matches nothing outside that; this SDK resolved past both ends, so a single saved rule served different variations to two users purely by which SDK their service ran. (#2500)

  • The two reachable shapes are a year-zero operand and an operand carried out of range by its offset. 0000-01-01 is inside the ISO grammar and is a real proleptic date (0000-02-29 exists — year 0 is divisible by 400), so neither #2480's grammar guard nor #2491's calendar-day check excluded it. Separately, [0-9]{4} constrains only the written year while a timezone offset moves the resolved instant, so 0001-01-01T00:00:00+05:00 fell below the floor and 9999-12-31T23:59:59-05:00 rose above the ceiling from years the grammar allows. The check therefore runs on the resolved instant — deliberately unlike #2491's, which runs on the written date. (#2500)

  • The exact boundaries remain accepted: 0001-01-01, 0001-01-01T05:00:00+05:00, 9999-12-31T23:59:59Z and 9999-12-31T18:59:59-05:00 all still resolve. (#2500)

If you have a targeting rule using one of these operands, rewrite it as the date you meant. The Management API has rejected them on write since #2480 (PortableDateOperand round-trips every grammar-matched operand through the engine's own parser, so it inherits the range bound), meaning only rules saved before that release can carry one.

  • The first SSE reconnect after a healthy stream drops is now jittered to [d/2, d], like every other backoff level. The drops this absorbs are fleet-wide — a single edge event severs every stream at once — so every client re-entered the backoff together and waited an identical delay, republishing the drop's own synchronisation as a reconnect spike one backoff later. Measured in production: a drop spread across 2.5–3.0 ms produced a reconnect spread of 26–46 ms. The delay never exceeds the previous one and stays strictly positive, so a stream that fails immediately still cannot busy-loop. (#2508)