smplkit 3.0.46 → 3.0.47
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/lib/smplkit/config/client.rb +172 -10
- data/lib/smplkit/management/buffer.rb +72 -0
- data/lib/smplkit/management/client.rb +75 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45143f97a9697f58ffc237a49e1c56c530d969a08f758acb969ed0d6ecd4c8fa
|
|
4
|
+
data.tar.gz: 2d678b90f48d3db9db2b8179af51e47357f96dc7d03d14eb4931e5f74543a7ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8be90684fe43d7ac3f61111c64ed98ad686e38028fdccdc28048418c7eda0e26378447417b28d5f6b3e5c4ab1af6c323b98a969244511361a41b4f8834b86a20
|
|
7
|
+
data.tar.gz: 4b8e13b2b1a7646910c19cce3542695bdd1fcfd76e5598c27b25b77c166b89f31239af2c2234ce1b57b2f3b6f78a3a4cf7ccf91a5c0845614d8b5a9fb29f7d30
|
|
@@ -25,21 +25,24 @@ module Smplkit
|
|
|
25
25
|
|
|
26
26
|
# A live, dot-accessible view over a resolved configuration.
|
|
27
27
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
28
|
+
# Identity-stable per config key (the same instance is returned by
|
|
29
|
+
# repeat +client.config.get+ / +get_or_create+ calls). Every read goes
|
|
30
|
+
# through the underlying client's resolved-config cache, so WebSocket
|
|
31
|
+
# updates are picked up automatically — there is no +subscribe+ step.
|
|
31
32
|
class LiveConfigProxy
|
|
32
33
|
def initialize(client, key)
|
|
33
34
|
@client = client
|
|
34
35
|
@key = key
|
|
35
|
-
@snapshot = client._resolve_now(key)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def config_id = @key
|
|
39
|
+
|
|
38
40
|
def get(item_key, default = nil)
|
|
39
|
-
|
|
41
|
+
snapshot = current_values
|
|
42
|
+
return snapshot if item_key.nil?
|
|
40
43
|
|
|
41
44
|
keys = item_key.to_s.split(".")
|
|
42
|
-
keys.reduce(
|
|
45
|
+
keys.reduce(snapshot) do |scope, k|
|
|
43
46
|
break default if scope.nil?
|
|
44
47
|
|
|
45
48
|
scope.is_a?(Hash) ? scope[k] : default
|
|
@@ -51,13 +54,82 @@ module Smplkit
|
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def to_h
|
|
54
|
-
|
|
57
|
+
current_values.dup
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def refresh
|
|
58
|
-
|
|
61
|
+
# The cache is fully invalidated for this key — the next read
|
|
62
|
+
# re-resolves from the parent client.
|
|
63
|
+
@client._invalidate(@key)
|
|
59
64
|
self
|
|
60
65
|
end
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Typed getters (ADR-037 §2.13)
|
|
69
|
+
#
|
|
70
|
+
# Each registers the item (key, type, default, description) on first
|
|
71
|
+
# call within the process, then returns the resolved value. When the
|
|
72
|
+
# resolved value can't be coerced to the getter's type — including
|
|
73
|
+
# the "not yet set on the server" case — the in-code default is
|
|
74
|
+
# returned and a debug message is logged.
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def get_bool(item_key, default, description: nil)
|
|
78
|
+
register_item(item_key, "BOOLEAN", default, description)
|
|
79
|
+
value = current_values[item_key.to_s]
|
|
80
|
+
return default unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
81
|
+
|
|
82
|
+
value
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def get_int(item_key, default, description: nil)
|
|
86
|
+
register_item(item_key, "NUMBER", default, description)
|
|
87
|
+
value = current_values[item_key.to_s]
|
|
88
|
+
return default if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
89
|
+
return value if value.is_a?(Integer)
|
|
90
|
+
return value.to_i if value.is_a?(Float) && value == value.floor
|
|
91
|
+
|
|
92
|
+
default
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def get_float(item_key, default, description: nil)
|
|
96
|
+
register_item(item_key, "NUMBER", default, description)
|
|
97
|
+
value = current_values[item_key.to_s]
|
|
98
|
+
return default if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
99
|
+
return value.to_f if value.is_a?(Numeric)
|
|
100
|
+
|
|
101
|
+
default
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def get_string(item_key, default, description: nil)
|
|
105
|
+
register_item(item_key, "STRING", default, description)
|
|
106
|
+
value = current_values[item_key.to_s]
|
|
107
|
+
value.is_a?(String) ? value : default
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def get_json(item_key, default, description: nil)
|
|
111
|
+
register_item(item_key, "JSON", default, description)
|
|
112
|
+
snap = current_values
|
|
113
|
+
snap.key?(item_key.to_s) ? snap[item_key.to_s] : default
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def on_change(item_key = nil, &)
|
|
117
|
+
if item_key.nil?
|
|
118
|
+
@client.on_change(@key, &)
|
|
119
|
+
else
|
|
120
|
+
@client.on_change_item(@key, item_key.to_s, &)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def current_values
|
|
127
|
+
@client._resolve_now(@key)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def register_item(item_key, item_type, default, description)
|
|
131
|
+
@client._observe_item_declaration(@key, item_key.to_s, item_type, default, description)
|
|
132
|
+
end
|
|
61
133
|
end
|
|
62
134
|
|
|
63
135
|
# Synchronous config runtime namespace.
|
|
@@ -75,8 +147,10 @@ module Smplkit
|
|
|
75
147
|
|
|
76
148
|
@snapshots = {}
|
|
77
149
|
@raw_chains = {}
|
|
150
|
+
@proxies = {}
|
|
78
151
|
@global_listeners = []
|
|
79
152
|
@key_listeners = Hash.new { |h, k| h[k] = [] }
|
|
153
|
+
@item_listeners = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = [] } }
|
|
80
154
|
@connected = false
|
|
81
155
|
@lock = Mutex.new
|
|
82
156
|
end
|
|
@@ -85,6 +159,13 @@ module Smplkit
|
|
|
85
159
|
return if @connected
|
|
86
160
|
|
|
87
161
|
@environment = @parent._environment
|
|
162
|
+
|
|
163
|
+
# Per ADR-037 §2.14: flush any buffered discovery declarations
|
|
164
|
+
# BEFORE the lazy init touches the runtime so newly-declared
|
|
165
|
+
# configs are visible to the very first +get+. The flush itself
|
|
166
|
+
# swallows server/network failures.
|
|
167
|
+
@manage&.config&.flush
|
|
168
|
+
|
|
88
169
|
@ws_manager = @parent._ensure_ws
|
|
89
170
|
@ws_manager.on("config_changed") { |data| handle_config_changed(data) }
|
|
90
171
|
@ws_manager.on("config_deleted") { |data| handle_config_deleted(data) }
|
|
@@ -95,11 +176,35 @@ module Smplkit
|
|
|
95
176
|
start unless @connected
|
|
96
177
|
|
|
97
178
|
snapshot = resolve(config_key)
|
|
179
|
+
raise Smplkit::NotFoundError, "Config #{config_key.inspect} not found" if snapshot.nil?
|
|
98
180
|
return snapshot if model_class.nil?
|
|
99
181
|
|
|
100
182
|
model_class.new(snapshot)
|
|
101
183
|
end
|
|
102
184
|
|
|
185
|
+
# Declare a configuration from code; return a live, dict-like view.
|
|
186
|
+
#
|
|
187
|
+
# Idempotent — repeat calls with the same +id+ return the same
|
|
188
|
+
# +LiveConfigProxy+ instance. The first call queues a discovery
|
|
189
|
+
# payload (the config and any items declared via typed getters on
|
|
190
|
+
# the returned handle) for upload to +POST /api/v1/configs/bulk+ on
|
|
191
|
+
# next flush. Unlike +#get+, this method does not raise +NotFoundError+
|
|
192
|
+
# when the id is absent — discovery handles that case.
|
|
193
|
+
def get_or_create(config_id, parent: nil, name: nil, description: nil)
|
|
194
|
+
parent_id =
|
|
195
|
+
case parent
|
|
196
|
+
when nil then nil
|
|
197
|
+
when String then parent
|
|
198
|
+
when LiveConfigProxy then parent.config_id
|
|
199
|
+
else
|
|
200
|
+
raise ArgumentError,
|
|
201
|
+
"parent must be a String id or LiveConfigProxy; got #{parent.class.name}"
|
|
202
|
+
end
|
|
203
|
+
_observe_config_declaration(config_id, parent: parent_id, name: name, description: description)
|
|
204
|
+
start unless @connected
|
|
205
|
+
cached_proxy(config_id)
|
|
206
|
+
end
|
|
207
|
+
|
|
103
208
|
def get_string(item_key, default: nil, config: nil)
|
|
104
209
|
typed_get(item_key, default, config) { |v| v.is_a?(String) ? v : v.to_s }
|
|
105
210
|
end
|
|
@@ -123,7 +228,7 @@ module Smplkit
|
|
|
123
228
|
def live(config_key)
|
|
124
229
|
start unless @connected
|
|
125
230
|
|
|
126
|
-
|
|
231
|
+
cached_proxy(config_key)
|
|
127
232
|
end
|
|
128
233
|
|
|
129
234
|
def on_change(config_key = nil, &block)
|
|
@@ -137,6 +242,13 @@ module Smplkit
|
|
|
137
242
|
block
|
|
138
243
|
end
|
|
139
244
|
|
|
245
|
+
def on_change_item(config_key, item_key, &block)
|
|
246
|
+
raise ArgumentError, "on_change_item requires a block" unless block
|
|
247
|
+
|
|
248
|
+
@item_listeners[config_key][item_key.to_s] << block
|
|
249
|
+
block
|
|
250
|
+
end
|
|
251
|
+
|
|
140
252
|
def refresh
|
|
141
253
|
@lock.synchronize do
|
|
142
254
|
@snapshots.clear
|
|
@@ -146,15 +258,46 @@ module Smplkit
|
|
|
146
258
|
end
|
|
147
259
|
|
|
148
260
|
def _resolve_now(config_key)
|
|
149
|
-
resolve(config_key)
|
|
261
|
+
resolve(config_key) || {}
|
|
150
262
|
end
|
|
151
263
|
|
|
152
264
|
def _close
|
|
153
265
|
# No durable resources; symmetry stub.
|
|
154
266
|
end
|
|
155
267
|
|
|
268
|
+
# Discard cached state for +config_key+; the next resolve will refetch.
|
|
269
|
+
def _invalidate(config_key)
|
|
270
|
+
@lock.synchronize do
|
|
271
|
+
@snapshots.delete(config_key)
|
|
272
|
+
@raw_chains.delete(config_key)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Internal: queue a config declaration with the management buffer.
|
|
277
|
+
def _observe_config_declaration(config_id, parent:, name:, description:)
|
|
278
|
+
@manage.config.register_config(
|
|
279
|
+
config_id,
|
|
280
|
+
service: @service,
|
|
281
|
+
environment: @environment,
|
|
282
|
+
parent: parent,
|
|
283
|
+
name: name,
|
|
284
|
+
description: description
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Internal: queue a config item declaration with the management buffer.
|
|
289
|
+
def _observe_item_declaration(config_id, item_key, item_type, default, description)
|
|
290
|
+
@manage.config.register_config_item(config_id, item_key, item_type, default, description)
|
|
291
|
+
end
|
|
292
|
+
|
|
156
293
|
private
|
|
157
294
|
|
|
295
|
+
def cached_proxy(config_key)
|
|
296
|
+
@lock.synchronize do
|
|
297
|
+
@proxies[config_key] ||= LiveConfigProxy.new(self, config_key)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
158
301
|
def typed_get(item_key, default, config_key)
|
|
159
302
|
snapshot = config_key ? resolve(config_key) : merged_snapshot
|
|
160
303
|
key = item_key.to_s
|
|
@@ -190,6 +333,12 @@ module Smplkit
|
|
|
190
333
|
end
|
|
191
334
|
|
|
192
335
|
chain = fetch_chain(config_key)
|
|
336
|
+
# An empty chain means the config does not exist on the server.
|
|
337
|
+
# Callers that hit +get(key)+ must raise +NotFoundError+; callers
|
|
338
|
+
# that hold a +LiveConfigProxy+ get an empty Hash from
|
|
339
|
+
# +_resolve_now+ so typed getters fall back to defaults.
|
|
340
|
+
return nil if chain.nil? || chain.empty?
|
|
341
|
+
|
|
193
342
|
snapshot = Helpers.resolve_chain(chain, @environment)
|
|
194
343
|
@lock.synchronize do
|
|
195
344
|
@raw_chains[config_key] = chain
|
|
@@ -238,6 +387,19 @@ module Smplkit
|
|
|
238
387
|
rescue StandardError => e
|
|
239
388
|
Smplkit.debug("config", "listener raised: #{e.class}: #{e.message}")
|
|
240
389
|
end
|
|
390
|
+
# Item-scoped listeners — fire for every registered item on the
|
|
391
|
+
# changed config. We don't diff old vs new values here because
|
|
392
|
+
# the Ruby cache is invalidated wholesale per config; item-scoped
|
|
393
|
+
# listeners on this SDK fire on the "any change to this config"
|
|
394
|
+
# signal, mirroring the +LiveConfigProxy.on_change(item_key)+
|
|
395
|
+
# contract.
|
|
396
|
+
@item_listeners[config_key].each_value do |listeners|
|
|
397
|
+
listeners.each do |cb|
|
|
398
|
+
cb.call(event)
|
|
399
|
+
rescue StandardError => e
|
|
400
|
+
Smplkit.debug("config", "item listener raised: #{e.class}: #{e.message}")
|
|
401
|
+
end
|
|
402
|
+
end
|
|
241
403
|
end
|
|
242
404
|
|
|
243
405
|
def fire_change_listeners_all(source)
|
|
@@ -6,6 +6,7 @@ module Smplkit
|
|
|
6
6
|
CONTEXT_BATCH_FLUSH_SIZE = 100
|
|
7
7
|
FLAG_BATCH_FLUSH_SIZE = 50
|
|
8
8
|
LOGGER_BATCH_FLUSH_SIZE = 50
|
|
9
|
+
CONFIG_BATCH_FLUSH_SIZE = 50
|
|
9
10
|
|
|
10
11
|
# Thread-safe batch buffer for context registration.
|
|
11
12
|
class ContextRegistrationBuffer
|
|
@@ -89,6 +90,77 @@ module Smplkit
|
|
|
89
90
|
end
|
|
90
91
|
end
|
|
91
92
|
|
|
93
|
+
# Thread-safe batch buffer for config declarations. Mirrors Python's
|
|
94
|
+
# +_ConfigRegistrationBuffer+: per-config metadata is retained across
|
|
95
|
+
# flushes so post-drain deltas re-attribute correctly, and items are
|
|
96
|
+
# dedup'd per +(config_id, item_key)+ so an already-sent item is
|
|
97
|
+
# never re-sent.
|
|
98
|
+
class ConfigRegistrationBuffer
|
|
99
|
+
def initialize
|
|
100
|
+
@pending = {} # config_id -> { id:, items: {}, ...meta }
|
|
101
|
+
@meta = {} # config_id -> { service:, environment:, parent:, name:, description: }
|
|
102
|
+
@sent_items = {} # "#{config_id}::#{item_key}" -> true
|
|
103
|
+
@lock = Mutex.new
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Idempotent — first writer's metadata wins.
|
|
107
|
+
def declare(config_id, service:, environment:, parent: nil, name: nil, description: nil)
|
|
108
|
+
@lock.synchronize do
|
|
109
|
+
next if @meta.key?(config_id)
|
|
110
|
+
|
|
111
|
+
@meta[config_id] = {
|
|
112
|
+
service: service, environment: environment,
|
|
113
|
+
parent: parent, name: name, description: description
|
|
114
|
+
}
|
|
115
|
+
@pending[config_id] = build_entry(config_id)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Queue an item declaration for an already-declared config. Items
|
|
120
|
+
# already sent in a previous +drain+ are skipped.
|
|
121
|
+
def add_item(config_id, item_key, item_type, default, description = nil)
|
|
122
|
+
@lock.synchronize do
|
|
123
|
+
next unless @meta.key?(config_id)
|
|
124
|
+
next if @sent_items.key?("#{config_id}::#{item_key}")
|
|
125
|
+
|
|
126
|
+
entry = (@pending[config_id] ||= build_entry(config_id))
|
|
127
|
+
next if entry["items"].key?(item_key)
|
|
128
|
+
|
|
129
|
+
item = { "value" => default, "type" => item_type }
|
|
130
|
+
item["description"] = description unless description.nil?
|
|
131
|
+
entry["items"][item_key] = item
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns and clears the pending batch; records sent items.
|
|
136
|
+
def drain
|
|
137
|
+
@lock.synchronize do
|
|
138
|
+
entries = @pending.values
|
|
139
|
+
entries.each do |entry|
|
|
140
|
+
entry["items"].each_key { |item_key| @sent_items["#{entry["id"]}::#{item_key}"] = true }
|
|
141
|
+
end
|
|
142
|
+
@pending = {}
|
|
143
|
+
entries
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def pending_count
|
|
148
|
+
@lock.synchronize { @pending.size }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def build_entry(config_id)
|
|
154
|
+
meta = @meta[config_id]
|
|
155
|
+
entry = { "id" => config_id, "items" => {} }
|
|
156
|
+
%i[service environment parent name description].each do |k|
|
|
157
|
+
v = meta[k]
|
|
158
|
+
entry[k.to_s] = v unless v.nil?
|
|
159
|
+
end
|
|
160
|
+
entry
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
92
164
|
# Thread-safe batch buffer for logger discovery.
|
|
93
165
|
class LoggerRegistrationBuffer
|
|
94
166
|
def initialize
|
|
@@ -453,6 +453,59 @@ module Smplkit
|
|
|
453
453
|
class ConfigNamespace
|
|
454
454
|
def initialize(api_client)
|
|
455
455
|
@api = SmplkitGeneratedClient::Config::ConfigsApi.new(api_client)
|
|
456
|
+
@buffer = Management::ConfigRegistrationBuffer.new
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# ---------------------------------------------------------------
|
|
460
|
+
# Discovery API (ADR-037 §2.13/§2.14)
|
|
461
|
+
# ---------------------------------------------------------------
|
|
462
|
+
|
|
463
|
+
# Queue a configuration declaration for bulk-discovery upload.
|
|
464
|
+
# Called by +ConfigClient#get_or_create+. Threshold-flushes on a
|
|
465
|
+
# background thread once the pending buffer reaches the flush size.
|
|
466
|
+
def register_config(config_id, service:, environment:, parent: nil,
|
|
467
|
+
name: nil, description: nil)
|
|
468
|
+
@buffer.declare(config_id, service: service, environment: environment,
|
|
469
|
+
parent: parent, name: name, description: description)
|
|
470
|
+
trigger_background_flush_if_needed
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Queue a config item declaration. +register_config+ must have run
|
|
474
|
+
# first; items added without a prior declaration are dropped.
|
|
475
|
+
def register_config_item(config_id, item_key, item_type, default, description = nil)
|
|
476
|
+
@buffer.add_item(config_id, item_key, item_type, default, description)
|
|
477
|
+
trigger_background_flush_if_needed
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def pending_count
|
|
481
|
+
@buffer.pending_count
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Send any pending config declarations to
|
|
485
|
+
# +POST /api/v1/configs/bulk+. Per ADR-024 §2.9 the bulk endpoint is
|
|
486
|
+
# plan-limit-exempt; failures here never propagate to customer code.
|
|
487
|
+
def flush
|
|
488
|
+
batch = @buffer.drain
|
|
489
|
+
return if batch.empty?
|
|
490
|
+
|
|
491
|
+
items = batch.map do |entry|
|
|
492
|
+
SmplkitGeneratedClient::Config::ConfigBulkItem.new(
|
|
493
|
+
id: entry["id"],
|
|
494
|
+
service: entry["service"],
|
|
495
|
+
environment: entry["environment"],
|
|
496
|
+
parent: entry["parent"],
|
|
497
|
+
name: entry["name"],
|
|
498
|
+
description: entry["description"],
|
|
499
|
+
items: bulk_items_to_wire(entry["items"])
|
|
500
|
+
)
|
|
501
|
+
end
|
|
502
|
+
body = SmplkitGeneratedClient::Config::ConfigBulkRequest.new(configs: items)
|
|
503
|
+
begin
|
|
504
|
+
ErrorMapping.call { @api.bulk_register_configs(body) }
|
|
505
|
+
rescue StandardError => e
|
|
506
|
+
# Fire-and-forget per ADR-024 §2.9.
|
|
507
|
+
Smplkit.debug("registration", "config bulk register failed: #{e.class}: #{e.message}")
|
|
508
|
+
end
|
|
456
509
|
end
|
|
457
510
|
|
|
458
511
|
def list(page_number: nil, page_size: nil)
|
|
@@ -584,6 +637,28 @@ module Smplkit
|
|
|
584
637
|
|
|
585
638
|
{ "id" => config.id, "items" => items_hash, "environments" => environments }
|
|
586
639
|
end
|
|
640
|
+
|
|
641
|
+
def bulk_items_to_wire(items_hash)
|
|
642
|
+
return nil if items_hash.nil? || items_hash.empty?
|
|
643
|
+
|
|
644
|
+
items_hash.transform_values do |def_hash|
|
|
645
|
+
SmplkitGeneratedClient::Config::ConfigItemDefinition.new(
|
|
646
|
+
value: def_hash["value"],
|
|
647
|
+
type: def_hash["type"],
|
|
648
|
+
description: def_hash["description"]
|
|
649
|
+
)
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def trigger_background_flush_if_needed
|
|
654
|
+
return unless @buffer.pending_count >= Management::CONFIG_BATCH_FLUSH_SIZE
|
|
655
|
+
|
|
656
|
+
Thread.new do
|
|
657
|
+
flush
|
|
658
|
+
rescue StandardError => e
|
|
659
|
+
Smplkit.debug("registration", "threshold config flush failed: #{e.class}: #{e.message}")
|
|
660
|
+
end
|
|
661
|
+
end
|
|
587
662
|
end
|
|
588
663
|
|
|
589
664
|
class FlagsNamespace
|