smplkit 2.0.2 → 2.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18065d9f25f15af5d853a49d46f1263388359c7acaffa765ca7b98e5d2420a98
4
- data.tar.gz: 7f386e4671aa2d5e339264007ba46a7b41c9a9cfa47ff1580514aee3248ebd86
3
+ metadata.gz: 30020aa708d634ffe321a30d974c3f7963eedb3f0c02008d37989bd956a8cefe
4
+ data.tar.gz: bee093803b8614ab3d36753f59b99ddbc10b012ccd0718f4fdf7b28e1b322baa
5
5
  SHA512:
6
- metadata.gz: e57e30ebc91651ba5376fc6fde90f5ba4550320470d5e9ae7192b15d4b2e5ce0a5492cdc753f2d04892f897cd7d29422c42c6c3e96008a675c53f1a9dfd24983
7
- data.tar.gz: 03eca6e00372adf7cebbdebc719f98ffdc3b2ae0768ba788e1205d29abbeeef8755e32a58a14cee8ac52685274de53ecd15bad4c8c07009fac99f4bd7dcfd132
6
+ metadata.gz: '0397940b7bfab7871f4f8c9e7e108ba16d82f4f2b7aa444af185a8caa9d095d183e3f7ae011d0e8b45a6d363d3188dcc832cb7268b7ee2f68dbda4a44b548135'
7
+ data.tar.gz: 356964cbc4b413c5e836866fabb98b335d0c2d8bc4ecbb6f61949a641297bc2b4aa326c52c838e82a075288b57540fd4f56d874dffae923dde261e888a054fbc
@@ -79,6 +79,9 @@ module Smplkit
79
79
  # +mgmt.flags.*+. Per-request context is set via
80
80
  # +client.set_context([...])+.
81
81
  class FlagsClient
82
+ INITIAL_START_RETRY_DELAY = 1.0
83
+ MAX_START_RETRY_DELAY = 60.0
84
+
82
85
  def initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:)
83
86
  @parent = parent
84
87
  @manage = manage
@@ -90,6 +93,9 @@ module Smplkit
90
93
 
91
94
  @flag_store = {}
92
95
  @connected = false
96
+ @ws_subscribed = false
97
+ @next_start_attempt_at = 0.0
98
+ @start_retry_delay = INITIAL_START_RETRY_DELAY
93
99
  @cache = ResolutionCache.new
94
100
  @handles = {}
95
101
  @global_listeners = []
@@ -116,24 +122,44 @@ module Smplkit
116
122
 
117
123
  # Eagerly initialize the flags subclient.
118
124
  #
119
- # Drains any pending flag-declaration buffer, fetches all flag
125
+ # Flushes any pending flag-declaration buffer, fetches all flag
120
126
  # definitions, opens the shared WebSocket and subscribes to
121
127
  # +flag_changed+ / +flag_deleted+ / +flags_changed+ events.
122
128
  #
123
129
  # Idempotent — safe to call multiple times. Called automatically on
124
130
  # first +flag.get+ evaluation if not invoked manually.
131
+ #
132
+ # If the flags-service is unhealthy (e.g. a coordinated rebuild where
133
+ # the app pod starts before the schema is loaded), the flush or refresh
134
+ # will fail. Pending declarations stay queued, the client remains
135
+ # disconnected, and the next call retries after an exponentially
136
+ # backed-off delay (capped at +MAX_START_RETRY_DELAY+ seconds).
137
+ # Evaluations during that window fall back to handle defaults.
125
138
  def start
126
139
  return if @connected
140
+ return if Process.clock_gettime(Process::CLOCK_MONOTONIC) < @next_start_attempt_at
127
141
 
128
142
  @environment = @parent._environment
129
- flush_flags_safely
130
- refresh
143
+
144
+ begin
145
+ @manage.flags.flush
146
+ refresh
147
+ rescue StandardError => e
148
+ schedule_start_retry(e)
149
+ return
150
+ end
151
+
131
152
  @connected = true
153
+ @start_retry_delay = INITIAL_START_RETRY_DELAY
154
+ @next_start_attempt_at = 0.0
132
155
 
133
156
  @ws_manager = @parent._ensure_ws
157
+ return if @ws_subscribed
158
+
134
159
  @ws_manager.on("flag_changed") { |data| handle_flag_changed(data) }
135
160
  @ws_manager.on("flag_deleted") { |data| handle_flag_deleted(data) }
136
161
  @ws_manager.on("flags_changed") { |data| handle_flags_changed(data) }
162
+ @ws_subscribed = true
137
163
  end
138
164
 
139
165
  def refresh
@@ -217,10 +243,12 @@ module Smplkit
217
243
  handle
218
244
  end
219
245
 
220
- def flush_flags_safely
221
- @manage.flags.flush
222
- rescue StandardError => e
223
- Smplkit.debug("registration", "bulk flag registration failed: #{e.class}: #{e.message}")
246
+ def schedule_start_retry(exc)
247
+ delay = @start_retry_delay
248
+ @next_start_attempt_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + delay
249
+ @start_retry_delay = [delay * 2, MAX_START_RETRY_DELAY].min
250
+ Smplkit.debug("registration",
251
+ "flags client start failed (will retry in #{delay}s): #{exc.class}: #{exc.message}")
224
252
  end
225
253
 
226
254
  def fetch_all_flags
@@ -42,6 +42,10 @@ module Smplkit
42
42
  end
43
43
 
44
44
  # Thread-safe batch buffer for flag declarations.
45
+ #
46
+ # Use +peek+ + +commit(ids)+ for the send path so a failed POST leaves
47
+ # declarations queued for the next attempt. +drain+ is unconditional and
48
+ # used only by tests/teardown.
45
49
  class FlagRegistrationBuffer
46
50
  def initialize
47
51
  @seen = {}
@@ -61,6 +65,17 @@ module Smplkit
61
65
  end
62
66
  end
63
67
 
68
+ def peek
69
+ @lock.synchronize { @pending.dup }
70
+ end
71
+
72
+ def commit(ids)
73
+ return if ids.nil? || ids.empty?
74
+
75
+ committed = ids.to_set
76
+ @lock.synchronize { @pending.reject! { |item| committed.include?(item["id"]) } }
77
+ end
78
+
64
79
  def drain
65
80
  @lock.synchronize do
66
81
  batch = @pending
@@ -538,11 +538,24 @@ module Smplkit
538
538
 
539
539
  def register(declaration)
540
540
  @buffer.add(declaration)
541
- flush if @buffer.pending_count >= Management::FLAG_BATCH_FLUSH_SIZE
541
+ return unless @buffer.pending_count >= Management::FLAG_BATCH_FLUSH_SIZE
542
+
543
+ begin
544
+ flush
545
+ rescue StandardError => e
546
+ Smplkit.debug("registration", "threshold flag flush failed: #{e.class}: #{e.message}")
547
+ end
542
548
  end
543
549
 
550
+ # POST pending declarations to the bulk endpoint.
551
+ #
552
+ # Items remain in the buffer until the request succeeds, so a flush
553
+ # against an unhealthy service is automatically retried by the next
554
+ # +flush+ call (lazy +start+ retry, periodic background flush, or
555
+ # final flush on close). Raises on failure — callers decide whether
556
+ # to retry.
544
557
  def flush
545
- batch = @buffer.drain
558
+ batch = @buffer.peek
546
559
  return if batch.empty?
547
560
 
548
561
  flag_items = batch.map do |entry|
@@ -553,8 +566,11 @@ module Smplkit
553
566
  end
554
567
  body = SmplkitGeneratedClient::Flags::FlagBulkRequest.new(flags: flag_items)
555
568
  ErrorMapping.call { @api.bulk_register_flags(body) }
556
- rescue StandardError => e
557
- Smplkit.debug("registration", "flag flush failed: #{e.class}: #{e.message}")
569
+ @buffer.commit(batch.map { |b| b["id"] })
570
+ end
571
+
572
+ def pending_count
573
+ @buffer.pending_count
558
574
  end
559
575
 
560
576
  def list
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smplkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smpl Solutions LLC