quonfig 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +44 -3
- data/lib/quonfig/client.rb +382 -33
- data/lib/quonfig/config_loader.rb +123 -38
- data/lib/quonfig/config_shape.rb +134 -0
- data/lib/quonfig/config_store.rb +65 -19
- data/lib/quonfig/datadir.rb +5 -5
- data/lib/quonfig/errors/initialization_error.rb +31 -0
- data/lib/quonfig/errors/initialization_timeout_error.rb +9 -4
- data/lib/quonfig/evaluation_details.rb +3 -0
- data/lib/quonfig/http_connection.rb +1 -1
- data/lib/quonfig/internal_logger.rb +21 -14
- data/lib/quonfig/options.rb +50 -2
- data/lib/quonfig/resolver.rb +26 -17
- data/lib/quonfig/sse_config_client.rb +128 -33
- data/lib/quonfig/version.rb +1 -1
- data/lib/quonfig/weighted_value_resolver.rb +3 -1
- data/lib/quonfig.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 774d181106fd045eddb916eb177c89744a97f014d7168a4e89d4d51ca2b3d6e2
|
|
4
|
+
data.tar.gz: c34620585b746c0f013ec473870306593e45fd617b62187ebc31a3d44e14d709
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e56307ab528445682d284fc48b651dd40224ba007f245e6abc4816bbb0b329e1d8fc0f5d6eff6f73137d375b8abbaf47488f074c803fe1d2944d197c3911910
|
|
7
|
+
data.tar.gz: 8dcfd007ada4cd44417a1b57928a90bf1e47a33e609b2cc14cb4484460911730f00d9e60cc0ef15fb150de61eab245d8e26e19e0c0f51e9ad0e50132afccbc03
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.0 - 2026-10-21
|
|
4
|
+
|
|
5
|
+
- **Fix (patch): the init deadline reason follows sdk-go (qfg-9p4i.17.14, design decision 10).** A new init attempt now starts whenever time remains before `init_timeout_ms`, however little. Before, a retry was skipped with under 250ms left and the boot was decided on the previous attempt, so an attempt that fails fast followed by a delivery that blackholes until the deadline raised `reason: :unavailable` where sdk-go raises `InitializationTimeoutError` (`reason: :timeout`). An attempt still in flight at the deadline decides the reason (`:timeout` only if none of its legs answered); a deadline that lands in the backoff is decided by the last completed attempt (`:timeout` only if every leg timed out). Test: `test/test_init_failure.rb`.
|
|
6
|
+
- **Fix (patch): init-failure conformance (qfg-9p4i.17.4).**
|
|
7
|
+
- `init_timeout_ms` <= 0 now runs exactly one fetch attempt, lets it finish, then applies `on_init_failure`. Before, the attempt got a 1ms budget, so it was cut off before it could answer and the boot raised `InitializationTimeoutError` even against a healthy delivery.
|
|
8
|
+
- Config-fetch log lines (including the per-leg lines logged during init) show `scheme://host:port`, never URL userinfo (`user:pass@`), and no longer log the body of a 401/403 response. A non-JSON 200 body no longer reaches the init error's `errors`. The wall-clock fetch timeout message also drops userinfo.
|
|
9
|
+
- The unauthorized init line and error report the primary leg's status, not whichever leg answered first.
|
|
10
|
+
- When the init deadline cuts off an attempt, the error summary keeps the errors of legs that already failed (e.g. `primary=...: HTTP 503`) instead of reporting every leg as "no response before initTimeout". Such an attempt counts as hung (`reason` `:timeout`) only if no leg failed fast; otherwise `reason` is `:unavailable`, as in sdk-go.
|
|
11
|
+
- A connect timeout (`Net::OpenTimeout`, which Faraday wraps in `ConnectionFailed`) counts as a hung leg, so a blackholed delivery host raises `InitializationTimeoutError` (`reason` `:timeout`), as sdk-go does for a dial timeout.
|
|
12
|
+
- **UPGRADE (minor): `on_init_failure` takes `:fail`, `:wait` or `:continue` (qfg-9p4i.10).** `:fail` is the default, as `:raise` was, so a boot that cannot load config still raises. What changes for you:
|
|
13
|
+
- `Client.new` now **retries** a fast failure (refused connection, DNS, 5xx) with a jittered 0.5s, 1s, 2s ... backoff until `init_timeout_ms` (default 10s) instead of raising on the first failed attempt. A boot during a short blip now succeeds, and a boot with delivery down raises after `init_timeout_ms`, not at once.
|
|
14
|
+
- When every delivery leg rejects the SDK key (401/403/404/410) the init call does not wait: `:fail` raises at once, `:continue` returns at once.
|
|
15
|
+
- The raised error is now `Quonfig::Errors::InitializationError` (`reason`: `:unavailable`, `:unauthorized` or `:timeout`; `errors`: the per-leg summary with hosts only). A hang still raises `Quonfig::Errors::InitializationTimeoutError`, now a subclass of it. **If you rescued `RuntimeError` around `Client.new`, rescue `Quonfig::Errors::InitializationError` instead**; it still descends from `StandardError` via `Quonfig::Error`.
|
|
16
|
+
- New `:wait` keeps blocking and retrying with no upper bound. `:continue` (the new name for `:return`) returns a client over an empty store that keeps retrying in the background.
|
|
17
|
+
- `:raise` and `:return` (and `ON_INITIALIZATION_FAILURE::RAISE` / `RETURN`) still work as deprecated aliases and log a one-time WARN. `Options#on_init_failure` now reads back the canonical value (`:fail` for `:raise`). An unknown value runs as `:fail` with a WARN.
|
|
18
|
+
- **Added (minor): `Client#wait_for_ready(timeout_ms: nil)`.** Blocks until the client has config; returns `true`, or `false` when the timeout runs out or the client is stopped. `ready?` is unchanged.
|
|
19
|
+
- **Changed (minor): init logging follows the cross-SDK contract.** A first failed attempt logs one WARN (`[quonfig] init: config not loaded yet, retrying until initTimeout (10s). primary=<host>: <error>; secondary=<host>: <error>`), then a WARN every 5s; the timeout logs one ERROR naming the options; a recovery logs `[quonfig] init recovered: ...` at WARN. A `:continue` client that is still not connected logs a WARN every 30s for 5 minutes, then every 5 minutes. The old `Initialization did not complete cleanly` line is gone. Hosts are logged, never the SDK key.
|
|
20
|
+
- **Fix (patch): a boot against refused legs no longer sometimes raises a timeout (qfg-9p4i.10).** When the init backoff ended a few ms before `init_timeout_ms`, the SDK started one more attempt with that few-ms budget; its Timeout guard fired mid-connect and the boot raised `InitializationTimeoutError` (`reason: :timeout`) instead of `InitializationError` with `reason: :unavailable` (about 1 boot in 40 with a 500ms timeout). A retry now starts only with at least 250ms left; otherwise the SDK waits out the deadline and decides on the last attempt's outcome. Test: `test/test_init_failure.rb`.
|
|
21
|
+
- **Added (minor): the `*_details` getters report `PROVIDER_NOT_READY` before config loads (qfg-9p4i.10, design B.3).** On a client with no config yet (`on_init_failure: :continue` before the background retry heals, or a caught init failure) `get_*_details` now return `reason: "ERROR"`, `error_code: "PROVIDER_NOT_READY"` (`Quonfig::EvaluationDetails::ERROR_PROVIDER_NOT_READY`) instead of `FLAG_NOT_FOUND`. `get(key, default)` still returns the default.
|
|
22
|
+
- **Changed (minor): init log lines (section C).** The progress WARN now fires every 5s through a long backoff, and the `:wait` timeout WARN fires at `init_timeout_ms` rather than after the attempt that straddles it. The auth line names the legs (`by primary and secondary`), and a heal through the stream names its leg (`via primary`) instead of `via stream`. With SemanticLogger, each init line carries the payload `event: "quonfig.init.<name>"` (`start`, `retrying`, `waiting`, `wait`, `failed`, `continue`, `unauthorized`, `not_connected`, `recovered`, `deprecated_alias`).
|
|
23
|
+
- **Fix (patch): a subclass of `Quonfig::Client` registers with the fork hook.** `register_instance` used the subclass's own (unset) registry and raised `NoMethodError` in `Client.new`.
|
|
24
|
+
- **Tests: unit and release tests now run integration-test-data `v2026.10.20`.** It adds the init-failure cases (fail by default, fail on refused/hung/401 legs, continue, and the `:raise`/`:return` aliases) and drops the Prefab-era keys. The chaos workflows stay on `v2026.10.16.2`.
|
|
25
|
+
|
|
26
|
+
## 1.9.0 - 2026-10-20
|
|
27
|
+
|
|
28
|
+
- **UPGRADE: SSE reports `connected` at the first frame, not at the 200 response (qfg-goi1.10.9).** A stream connection now counts as connected, for `connection_state`, the reconnect backoff and the fallback poller, only after it delivers its first valid frame: a config envelope or a keepalive. Before, it counted as soon as the 200 response headers arrived.
|
|
29
|
+
- Against Quonfig's servers the headers and the first frame arrive together, so you should see no difference.
|
|
30
|
+
- `connection_state` now reports `:disconnected` (then `:falling_back`) when the stream ends before any frame, where it used to keep reporting `:connected` on stale config; a keepalive now counts as a live stream, so a quiet workspace no longer starts fallback polling while its stream is healthy.
|
|
31
|
+
- A connection that closes before any frame is now a failed attempt. The reconnect delay grows, the client reports disconnected, and the fallback poller takes over after its usual threshold.
|
|
32
|
+
|
|
33
|
+
Before this change, a server that answered 200 and closed at once left the SDK on stale config indefinitely while `connection_state` reported `:connected`.
|
|
34
|
+
- **UPGRADE: may change returned values. Minor: one config that fails to load no longer raises out of every read of its key (qfg-krjt).** A config whose rule, criterion or default has the wrong shape (for example `rules: ["oops"]`, `criteria: "oops"` or `default: "oops"`), or whose weighted value has a `weight` that is not an integer (a string, float or null), used to install and then raise `NoMethodError` or `TypeError` out of `get`, `enabled?` and the typed getters on every read of that key. That entry is now skipped when the payload is decoded (HTTP, SSE or a datadir file) with a WARN naming its key, the rest installs, and the skipped key reads as not found (your default, `false` from `enabled?`, or `FLAG_NOT_FOUND` in the `*_details` getters). A payload whose every entry fails is still rejected (a failed fetch leg on HTTP, a dropped event on SSE, a load error in datadir mode), so it cannot install as an empty workspace. A weighted value with no `weight` now counts as 0 instead of raising. Matches sdk-go, sdk-node and sdk-python. Test: `test/test_malformed_config_skip.rb`.
|
|
35
|
+
- **Changed (minor): the first-frame rule in the SSE worker (qfg-goi1.10.43).** A keepalive (a comment-only block such as `: keepalive`) now resets the reconnect backoff to its initial delay and cancels the fallback poller's engage timer (or stops an engaged poller), like an envelope; it does not advance `last_successful_refresh`. A 200 stream that ends before its first frame now calls the internal `on_error` path (logged at WARN), so the poller engages at once if SSE never connected, or after 2x the poll interval following a mid-life drop. `connection_state` still stays `:connected` across a clean reconnect after a session that delivered a frame, but only until that next connection either delivers its own first frame or ends without one. Contract: integration-test-data `v2026.10.16.4`, supervisor contract Test 9 (a)-(d) and the Test 2 reset amendment, in `test/test_sse_first_frame.rb`.
|
|
36
|
+
- **Tests: unit and release tests now run integration-test-data `v2026.10.16.4`.** It also moves the two `get_or_raise` init-timeout cases to `http://192.0.2.1:1` (qfg-goi1.6). The chaos workflows stay on `v2026.10.16.2`.
|
|
37
|
+
- **Fix (patch): `Client#stop` no longer leaks the SSE worker thread or waits 2 seconds (qfg-yrly).** Stopping a client with a live stream left the SSE worker blocked forever: the worker left the stream with a `break` inside `read_body`, and Net::HTTP then read the rest of the endless stream to finish the response. `stop` waited 2s for the worker and then returned with the thread still running, and the thread kept the old client alive in memory. The SDK also closes the stream when it enters auth-failed, so each auth-failed entry leaked one thread too. The worker now leaves the stream without that read, so `stop` returns promptly and the thread exits. Tests: `test/test_sse_close_during_stream.rb` (close during a live stream and during a blocked read), and the close assertion in `test/test_sse_config_client.rb` now checks the real worker thread.
|
|
38
|
+
- **Fix (patch): a config update is now applied to the store all at once (qfg-i86q).** The SDK wrote each config of a new envelope into the live store one key at a time, so a request evaluating during that write could see a flag from the new envelope with a segment from the old one, or a key that was briefly missing, and get the wrong value with no error or log. Each install (SSE, polling, the initial fetch and a datadir reload) now builds the new set of configs first and swaps it in with one write, like sdk-go, and each lookup reads one snapshot from start to finish (the flag, its segments and any `decryptWith` key). `ConfigStore` gains `replace_all`; `get`, `set`, `delete`, `keys` and `all_configs` behave as before. Test: `test/test_config_loader_atomic_install.rb` (a reader during 198 installs; before this fix about 40% of its reads were wrong).
|
|
39
|
+
|
|
3
40
|
## 1.8.0 - 2026-10-16
|
|
4
41
|
|
|
5
42
|
- **UPGRADE: retry cadence and log levels change after a rejected 401/403/404/410 fetch cycle; a revoked key logs one config ERROR. `terminal_failure?` changes meaning. Minor (1.8.0): one auth-failure rule across the SDKs (qfg-goi1.10.26). This reverses the qfg-i5xv latch.**
|
|
@@ -14,6 +51,7 @@
|
|
|
14
51
|
- **Whole-number floats compare as plain digits.** In `PROP_IS_ONE_OF` / `PROP_IS_NOT_ONE_OF`, the starts-with / ends-with / contains operators and `HIERARCHICAL_MATCH`, a context value of `1.0`, `1234567.0` or `5e9` now reads as `"1"`, `"1234567"`, `"5000000000"` (it was `"1.0"`, `"1234567.0"`, `"5.0e9"`), so `is one of ["1234567"]` matches it and a list holding `"1.0"` no longer does. Non-integral floats (`1.5`) and Integers are unchanged; booleans still read `"true"` / `"false"`.
|
|
15
52
|
- **Abbreviated versions.** `PROP_SEMVER_*` reads `"2"` as `2.0.0` and `"2.0"` / `"4.1"` as `2.0.0` / `4.1.0`, on the context and the rule side, so an app reporting `"4.1"` now matches a `4.1.0` rule. A prerelease or build suffix still needs the full `x.y.z`, and a `v` prefix is still not a version. `SemanticVersion.parse` itself stays strict.
|
|
16
53
|
Matches integration-test-data `v2026.10.16.1` and the other SDKs.
|
|
54
|
+
Approved as minors under Decision 1 (qfg-goi1.5, 2026-10-09).
|
|
17
55
|
- **UPGRADE: may change returned values. Minor: JSON values are returned as a fresh copy on every call (qfg-goi1.10.3).** `get`, `get_json` and `get_json_details` (also on `BoundClient`) used to return the SDK's own Hash or Array for a JSON config. If your code changed the result (for example added a key or appended to a list), every later read in the process saw the change. Each call now returns its own copy of the Hashes and Arrays, so changes stay local. What changes for you: two calls no longer return the same object (`equal?` is false; `==` is still true), and code that relied on mutating the result to change config no longer does. Values are not frozen, so code that mutates its own copy keeps working. Strings are not copied: `get_string` and the strings inside a JSON value are the same objects as before. Matches sdk-go, sdk-net and sdk-java.
|
|
18
56
|
- **CI: unit and release tests now run integration-test-data `v2026.10.16.1` (qfg-goi1.5.7).** It adds the OPERATOR EDGES cases (bool and digit-string one-of, int ranges, prerelease and abbreviated semver, strict dates). The chaos workflows stay on `v2026.10.03`.
|
|
19
57
|
- **Fix (patch): `EnvVarParseError` messages now use the cross-SDK wording (qfg-goi1.10.21).** An ENV_VAR value that cannot be converted to the config's type now raises with `environment variable "<NAME>" cannot be converted to <type> for config "<key>"` (for example `... cannot be converted to int for config "my.key"`), the template pinned by integration-test-data `v2026.10.12`. It was `Evaluating <key> couldn't coerce <NAME> to <type>`. A malformed stored duration reads `stored value cannot be converted to duration for config "<key>"`. The value is still never in the message. Only the message text changes; the exception class and when it is raised are unchanged.
|
data/README.md
CHANGED
|
@@ -259,6 +259,45 @@ for the cross-SDK story (sdk-node, sdk-go, sdk-ruby, sdk-python, sdk-java).
|
|
|
259
259
|
| `QUONFIG_DEV_CONTEXT` | `false` turns off the developer context (`quonfig-user.email` from `qfg login`). An explicit `enable_quonfig_user_context:` wins. See [Developer context](#developer-context-on-by-default). |
|
|
260
260
|
| `QUONFIG_DOMAIN` | Base domain used to derive api, sse, and telemetry URLs. Defaults to `quonfig.com`. Set to `quonfig-staging.com` to point at staging. Explicit `api_urls:` / `telemetry_url:` kwargs override this. |
|
|
261
261
|
|
|
262
|
+
## Startup failure (`on_init_failure`)
|
|
263
|
+
|
|
264
|
+
`Client.new` fetches config from both delivery legs (primary, then the
|
|
265
|
+
secondary in parallel) and retries through blips with a jittered backoff
|
|
266
|
+
(0.5s, 1s, 2s ... capped at 30s) for up to `init_timeout_ms` (default 10s).
|
|
267
|
+
A healthy boot logs nothing above DEBUG. If no config has loaded by then,
|
|
268
|
+
`on_init_failure` decides:
|
|
269
|
+
|
|
270
|
+
| Value | At `init_timeout_ms` | Every leg rejects the SDK key (401/403/404/410) |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `:fail` (default) | Raises `Quonfig::Errors::InitializationError` (`reason` `:unavailable`), or its subclass `InitializationTimeoutError` (`reason` `:timeout`) when an attempt was still in flight. No client is returned, so your orchestrator's restart is the retry. | Raises at once (`reason` `:unauthorized`). |
|
|
273
|
+
| `:wait` | Keeps blocking and retrying, with no upper bound, logging a WARN every 30s. | Logs an ERROR and keeps retrying. |
|
|
274
|
+
| `:continue` | Returns a client with no config: getters return your default (and `get_or_raise` raises `MissingDefaultError`). It keeps retrying in the background and serves live config as soon as it lands. | Returns at once. |
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
begin
|
|
278
|
+
client = Quonfig::Client.new(sdk_key: key, on_init_failure: :fail, init_timeout_ms: 10_000)
|
|
279
|
+
rescue Quonfig::Errors::InitializationError => e
|
|
280
|
+
e.reason # => :unavailable, :unauthorized or :timeout
|
|
281
|
+
e.errors # => "primary=primary.quonfig.com:443: ...; secondary=secondary.quonfig.com:443: ..."
|
|
282
|
+
raise
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`init_timeout_ms` <= 0 runs exactly one fetch attempt and lets it finish (it
|
|
287
|
+
is bounded by the per-leg fetch timeouts), then applies `on_init_failure` as
|
|
288
|
+
in the table: `:fail` raises, `:continue` starts on code defaults and keeps
|
|
289
|
+
retrying, `:wait` keeps retrying. It never means "no deadline".
|
|
290
|
+
|
|
291
|
+
Init log lines and `InitializationError#message` / `#errors` name delivery
|
|
292
|
+
hosts (`host:port`) and HTTP statuses only, never URL credentials
|
|
293
|
+
(`user:pass@`) or upstream response bodies.
|
|
294
|
+
|
|
295
|
+
Readiness: `client.ready?` is true once config has loaded, and
|
|
296
|
+
`client.wait_for_ready(timeout_ms: nil)` blocks until it has (true) or the
|
|
297
|
+
timeout runs out (false). Under `:wait`, point your readiness probe at
|
|
298
|
+
`ready?`. `:raise` and `:return` are the deprecated spellings of `:fail` and
|
|
299
|
+
`:continue`; they still work and log a one-time WARN.
|
|
300
|
+
|
|
262
301
|
## Constructor options
|
|
263
302
|
|
|
264
303
|
```ruby
|
|
@@ -270,6 +309,7 @@ Quonfig::Client.new(
|
|
|
270
309
|
fallback_poll_enabled: true,
|
|
271
310
|
fallback_poll_interval_ms: 60_000,
|
|
272
311
|
init_timeout_ms: 10_000,
|
|
312
|
+
on_init_failure: :fail,
|
|
273
313
|
on_no_default: :raise,
|
|
274
314
|
global_context: {},
|
|
275
315
|
datadir: '/path/to/workspace',
|
|
@@ -287,7 +327,8 @@ Quonfig::Client.new(
|
|
|
287
327
|
| `enable_sse` | `Boolean` | `true` | Receive real-time updates over Server-Sent Events. |
|
|
288
328
|
| `fallback_poll_enabled` | `Boolean` | `true` | Engage HTTP polling as a fallback when SSE is unavailable for >= 2x `fallback_poll_interval_ms`. Deprecated alias: `enable_polling`. |
|
|
289
329
|
| `fallback_poll_interval_ms` | `Integer` (ms) | `60_000` | Interval between fallback HTTP polls, in milliseconds. Deprecated alias: `poll_interval` (seconds, multiplied by 1000 internally). |
|
|
290
|
-
| `init_timeout_ms` | `Integer` (ms) | `10_000` | Maximum time to wait for the initial config load, in milliseconds. Deprecated alias: `initialization_timeout_sec` (seconds, multiplied by 1000 internally). |
|
|
330
|
+
| `init_timeout_ms` | `Integer` (ms) | `10_000` | Maximum time to wait for the initial config load, in milliseconds. `<= 0` runs exactly one attempt, then applies `on_init_failure`. Deprecated alias: `initialization_timeout_sec` (seconds, multiplied by 1000 internally). |
|
|
331
|
+
| `on_init_failure` | `Symbol` | `:fail` | What `Client.new` does when no config has loaded by `init_timeout_ms`: `:fail`, `:wait` or `:continue`. See [Startup failure](#startup-failure-on_init_failure). Deprecated aliases: `:raise` (= `:fail`), `:return` (= `:continue`). |
|
|
291
332
|
| `on_no_default` | `Symbol` | `:raise` | Behavior when a key has no value and no default: `:raise` (raise `Quonfig::Errors::MissingDefaultError`) or `:return_nil`. |
|
|
292
333
|
| `global_context` | `Hash` | `{}` | Context applied to every evaluation. |
|
|
293
334
|
| `enable_quonfig_user_context` | `Boolean` or `nil` | `nil` (on, unless `QUONFIG_DEV_CONTEXT=false`) | Add the `qfg login` email as `quonfig-user.email`. See [Developer context](#developer-context-on-by-default). |
|
|
@@ -500,7 +541,7 @@ Caveats:
|
|
|
500
541
|
in-process SSE state is moot.
|
|
501
542
|
- The first lookup in a forked child **blocks** on that child's own config
|
|
502
543
|
fetch, under the same `init_timeout_ms` and `on_init_failure` options a
|
|
503
|
-
fresh client uses. **The default is `on_init_failure: :
|
|
544
|
+
fresh client uses. **The default is `on_init_failure: :fail`**, so if the
|
|
504
545
|
child's fetch fails against every `api_urls` leg (primary and secondary
|
|
505
546
|
both unreachable) the failure **raises out of that first lookup**, exactly
|
|
506
547
|
as `Client.new` would at boot, and later lookups keep raising — without
|
|
@@ -508,7 +549,7 @@ Caveats:
|
|
|
508
549
|
the client serves config normally again. On 1.3.0 and earlier a child in
|
|
509
550
|
that situation silently served the parent's snapshot instead. If you would
|
|
510
551
|
rather a forked child serve defaults through an outage, set
|
|
511
|
-
`on_init_failure: :
|
|
552
|
+
`on_init_failure: :continue`: a failed fetch then logs one line and the child
|
|
512
553
|
serves defaults until its stream or poller lands the first envelope.
|
|
513
554
|
- **Other threads wait.** Every thread that reaches the client while that
|
|
514
555
|
first fetch is in flight blocks on it and then sees the fetched config. One
|