launchdarkly-server-sdk 8.18.0 → 8.18.2
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/ldclient-rb/impl/big_segments.rb +28 -7
- data/lib/ldclient-rb/impl/data_source/polling.rb +7 -4
- data/lib/ldclient-rb/impl/data_source/status_provider.rb +35 -2
- data/lib/ldclient-rb/impl/data_source/stream.rb +9 -4
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +21 -14
- data/lib/ldclient-rb/impl/data_system/streaming.rb +10 -6
- data/lib/ldclient-rb/impl/expiring_cache.rb +11 -3
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +8 -6
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +4 -1
- data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -2
- data/lib/ldclient-rb/impl/repeating_task.rb +4 -2
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +13 -7
- data/lib/ldclient-rb/impl/util.rb +11 -0
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bda19fb6334d96931ca31147df9221684aeaf91f6093b8ea29a3f7abc9437f2b
|
|
4
|
+
data.tar.gz: a419f8ad70cf484c1d2c5d82dd6c475a052cc253aacc3b93452da4efc4986438
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 136cd5c90b0ed9f3d8a2e2c6c7232e097e820ecb7c3c39bdbfb527de7233d3d58dbedf84eca06e1315d0e8164da9f8a2fbf6b89db4c6d04646da0ffb82bbface
|
|
7
|
+
data.tar.gz: fd15cd49b22cd81d7f2479b97befd138f717055f64b540da47d892f604552589dc38b79eaf60c50a2b4a76b9f2b7cb38252492e2af2caeab9eeffdce0882404c
|
|
@@ -21,6 +21,7 @@ module LaunchDarkly
|
|
|
21
21
|
@status_provider = BigSegmentStoreStatusProviderImpl.new(-> { get_status })
|
|
22
22
|
@logger = logger
|
|
23
23
|
@last_status = nil
|
|
24
|
+
@poll_lock = Mutex.new
|
|
24
25
|
|
|
25
26
|
unless @store.nil?
|
|
26
27
|
@cache = ExpiringCache.new(big_segments_config.context_cache_size, big_segments_config.context_cache_time)
|
|
@@ -49,18 +50,41 @@ module LaunchDarkly
|
|
|
49
50
|
return BigSegmentMembershipResult.new(nil, BigSegmentsStatus::STORE_ERROR)
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
|
-
|
|
53
|
-
unless
|
|
53
|
+
status = get_status
|
|
54
|
+
unless status.available
|
|
54
55
|
return BigSegmentMembershipResult.new(membership, BigSegmentsStatus::STORE_ERROR)
|
|
55
56
|
end
|
|
56
|
-
BigSegmentMembershipResult.new(membership,
|
|
57
|
+
BigSegmentMembershipResult.new(membership, status.stale ? BigSegmentsStatus::STALE : BigSegmentsStatus::HEALTHY)
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def get_status
|
|
60
|
-
@last_status
|
|
61
|
+
status = @last_status
|
|
62
|
+
return status if status
|
|
63
|
+
|
|
64
|
+
new_status = @poll_lock.synchronize do
|
|
65
|
+
# Another caller may have finished a poll while we waited for the lock.
|
|
66
|
+
status = @last_status
|
|
67
|
+
return status if status
|
|
68
|
+
|
|
69
|
+
query_store_status
|
|
70
|
+
end
|
|
71
|
+
@status_provider.update_status(new_status)
|
|
72
|
+
|
|
73
|
+
new_status
|
|
61
74
|
end
|
|
62
75
|
|
|
63
76
|
def poll_store_and_update_status
|
|
77
|
+
new_status = @poll_lock.synchronize { query_store_status }
|
|
78
|
+
@status_provider.update_status(new_status)
|
|
79
|
+
|
|
80
|
+
new_status
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Queries the store and caches the result. Callers hold @poll_lock, so this must not notify
|
|
85
|
+
# observers - a listener that calls back into the manager would deadlock on the mutex.
|
|
86
|
+
#
|
|
87
|
+
private def query_store_status
|
|
64
88
|
new_status = Interfaces::BigSegmentStoreStatus.new(false, false) # default to "unavailable" if we don't get a new status below
|
|
65
89
|
unless @store.nil?
|
|
66
90
|
begin
|
|
@@ -71,9 +95,6 @@ module LaunchDarkly
|
|
|
71
95
|
end
|
|
72
96
|
end
|
|
73
97
|
@last_status = new_status
|
|
74
|
-
@status_provider.update_status(new_status)
|
|
75
|
-
|
|
76
|
-
new_status
|
|
77
98
|
end
|
|
78
99
|
|
|
79
100
|
def stale?(timestamp)
|
|
@@ -38,14 +38,17 @@ module LaunchDarkly
|
|
|
38
38
|
begin
|
|
39
39
|
all_data, headers = request_all_data
|
|
40
40
|
DataSource.record_environment_id(@config.data_source_update_sink, headers)
|
|
41
|
+
newly_initialized = false
|
|
41
42
|
if all_data
|
|
42
43
|
update_sink_or_data_store.init(all_data)
|
|
43
|
-
|
|
44
|
-
@config.logger.info { "[LDClient] Polling connection initialized" }
|
|
45
|
-
@ready.set
|
|
46
|
-
end
|
|
44
|
+
newly_initialized = @initialized.make_true
|
|
47
45
|
end
|
|
48
46
|
@config.data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::VALID, nil)
|
|
47
|
+
|
|
48
|
+
if newly_initialized
|
|
49
|
+
@config.logger.info { "[LDClient] Polling connection initialized" }
|
|
50
|
+
@ready.set
|
|
51
|
+
end
|
|
49
52
|
rescue JSON::ParserError => e
|
|
50
53
|
@config.logger.error { "[LDClient] JSON parsing failed for polling response." }
|
|
51
54
|
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "concurrent"
|
|
4
4
|
require "forwardable"
|
|
5
|
+
require "ldclient-rb/impl/util"
|
|
5
6
|
require "ldclient-rb/interfaces"
|
|
6
7
|
|
|
7
8
|
module LaunchDarkly
|
|
@@ -19,18 +20,24 @@ module LaunchDarkly
|
|
|
19
20
|
extend Forwardable
|
|
20
21
|
def_delegators :@status_broadcaster, :add_listener, :remove_listener
|
|
21
22
|
|
|
23
|
+
MONOTONIC_CLOCK = -> { Impl::Util.monotonic_seconds }
|
|
24
|
+
private_constant :MONOTONIC_CLOCK
|
|
25
|
+
|
|
22
26
|
#
|
|
23
27
|
# Creates a new status provider.
|
|
24
28
|
#
|
|
25
29
|
# @param status_broadcaster [LaunchDarkly::Impl::Broadcaster] Broadcaster for status changes
|
|
30
|
+
# @param clock [#call] returns seconds on a monotonic clock; injectable for tests
|
|
26
31
|
#
|
|
27
|
-
def initialize(status_broadcaster)
|
|
32
|
+
def initialize(status_broadcaster, clock: MONOTONIC_CLOCK)
|
|
28
33
|
@status_broadcaster = status_broadcaster
|
|
34
|
+
@clock = clock
|
|
29
35
|
@status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
|
30
36
|
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
31
37
|
Time.now,
|
|
32
38
|
nil
|
|
33
39
|
)
|
|
40
|
+
@state_since_monotonic = @clock.call
|
|
34
41
|
@lock = Concurrent::ReadWriteLock.new
|
|
35
42
|
end
|
|
36
43
|
|
|
@@ -41,8 +48,29 @@ module LaunchDarkly
|
|
|
41
48
|
end
|
|
42
49
|
end
|
|
43
50
|
|
|
51
|
+
#
|
|
52
|
+
# The current status together with the seconds the data source has spent
|
|
53
|
+
# in its current state, read atomically under one lock acquisition so the
|
|
54
|
+
# duration can never be paired with a stale state.
|
|
55
|
+
#
|
|
56
|
+
# The duration is measured on the monotonic clock, so a wall-clock step
|
|
57
|
+
# cannot distort it. `status.state_since` remains the wall-clock time
|
|
58
|
+
# reported to applications; this duration is the value to use when it
|
|
59
|
+
# decides behavior.
|
|
60
|
+
#
|
|
61
|
+
# @private
|
|
62
|
+
# @return [Array(LaunchDarkly::Interfaces::DataSource::Status, Float)]
|
|
63
|
+
#
|
|
64
|
+
def status_and_seconds_in_state
|
|
65
|
+
@lock.with_read_lock do
|
|
66
|
+
[@status, @clock.call - @state_since_monotonic]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
44
70
|
# (see LaunchDarkly::Interfaces::DataSource::UpdateSink#update_status)
|
|
45
71
|
def update_status(new_state, new_error)
|
|
72
|
+
return if new_state.nil?
|
|
73
|
+
|
|
46
74
|
status_to_broadcast = nil
|
|
47
75
|
|
|
48
76
|
@lock.with_write_lock do
|
|
@@ -62,7 +90,12 @@ module LaunchDarkly
|
|
|
62
90
|
# No change if state is the same and no error
|
|
63
91
|
return if new_state == old_status.state && new_error.nil?
|
|
64
92
|
|
|
65
|
-
|
|
93
|
+
if new_state == old_status.state
|
|
94
|
+
new_since = @status.state_since
|
|
95
|
+
else
|
|
96
|
+
new_since = Time.now
|
|
97
|
+
@state_since_monotonic = @clock.call
|
|
98
|
+
end
|
|
66
99
|
new_error = @status.last_error if new_error.nil?
|
|
67
100
|
|
|
68
101
|
@status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
|
@@ -32,6 +32,7 @@ module LaunchDarkly
|
|
|
32
32
|
@stopped = Concurrent::AtomicBoolean.new(false)
|
|
33
33
|
@ready = Concurrent::Event.new
|
|
34
34
|
@connection_attempt_start_time = 0
|
|
35
|
+
@connection_attempt_started_monotonic = nil
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def initialized?
|
|
@@ -47,6 +48,7 @@ module LaunchDarkly
|
|
|
47
48
|
opts = {
|
|
48
49
|
headers: headers,
|
|
49
50
|
read_timeout: READ_TIMEOUT_SECONDS,
|
|
51
|
+
connect_timeout: @config.connect_timeout,
|
|
50
52
|
logger: @config.logger,
|
|
51
53
|
socket_factory: @config.socket_factory,
|
|
52
54
|
reconnect_time: @config.initial_reconnect_delay,
|
|
@@ -184,13 +186,16 @@ module LaunchDarkly
|
|
|
184
186
|
|
|
185
187
|
def log_connection_started
|
|
186
188
|
@connection_attempt_start_time = Impl::Util::current_time_millis
|
|
189
|
+
@connection_attempt_started_monotonic = Impl::Util.monotonic_seconds
|
|
187
190
|
end
|
|
188
191
|
|
|
189
192
|
def log_connection_result(is_success)
|
|
190
|
-
if !@diagnostic_accumulator.nil? &&
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
if !@diagnostic_accumulator.nil? && !@connection_attempt_started_monotonic.nil?
|
|
194
|
+
# The monotonic stamp is both the "attempt in flight" sentinel and the
|
|
195
|
+
# duration base; the wall-clock value is only the reported timestamp.
|
|
196
|
+
duration_millis = ((Impl::Util.monotonic_seconds - @connection_attempt_started_monotonic) * 1_000).to_i
|
|
197
|
+
@diagnostic_accumulator.record_stream_init(@connection_attempt_start_time, !is_success, duration_millis)
|
|
198
|
+
@connection_attempt_started_monotonic = nil
|
|
194
199
|
end
|
|
195
200
|
end
|
|
196
201
|
end
|
|
@@ -292,6 +292,10 @@ module LaunchDarkly
|
|
|
292
292
|
|
|
293
293
|
# Set ready event if and only if a selector is defined for the changeset.
|
|
294
294
|
if basis.change_set.selector && basis.change_set.selector.defined?
|
|
295
|
+
@data_source_status_provider.update_status(
|
|
296
|
+
LaunchDarkly::Interfaces::DataSource::Status::VALID,
|
|
297
|
+
nil
|
|
298
|
+
)
|
|
295
299
|
@ready_event.set
|
|
296
300
|
return fallback
|
|
297
301
|
end
|
|
@@ -459,10 +463,12 @@ module LaunchDarkly
|
|
|
459
463
|
break if update == "quit"
|
|
460
464
|
|
|
461
465
|
if update == "check"
|
|
462
|
-
# Check condition periodically
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
+
# Check condition periodically. One atomic read pairs the status with
|
|
467
|
+
# its monotonic time-in-state, so a verdict can never pair a stale
|
|
468
|
+
# state with a fresh duration.
|
|
469
|
+
current_status, seconds_in_state = @data_source_status_provider.status_and_seconds_in_state
|
|
470
|
+
return SyncResult::RECOVER if check_recovery && recovery_condition(current_status, seconds_in_state)
|
|
471
|
+
return SyncResult::FALLBACK if fallback_condition(current_status, seconds_in_state)
|
|
466
472
|
end
|
|
467
473
|
next
|
|
468
474
|
end
|
|
@@ -473,15 +479,14 @@ module LaunchDarkly
|
|
|
473
479
|
# Handle the update
|
|
474
480
|
@store.apply(update.change_set, true) if update.change_set
|
|
475
481
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
@ready_event.set
|
|
479
|
-
record_environment_id(update.environment_id)
|
|
480
|
-
end
|
|
482
|
+
valid = update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
|
|
483
|
+
record_environment_id(update.environment_id) if valid
|
|
481
484
|
|
|
482
485
|
# Update status
|
|
483
486
|
@data_source_status_provider.update_status(update.state, update.error)
|
|
484
487
|
|
|
488
|
+
@ready_event.set if valid
|
|
489
|
+
|
|
485
490
|
return SyncResult::FDV1 if update.fallback_to_fdv1
|
|
486
491
|
|
|
487
492
|
return SyncResult::REMOVE if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
|
|
@@ -514,13 +519,14 @@ module LaunchDarkly
|
|
|
514
519
|
# Determine if we should fallback to the next synchronizer.
|
|
515
520
|
#
|
|
516
521
|
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
522
|
+
# @param seconds_in_state [Float] monotonic seconds spent in status.state
|
|
517
523
|
# @return [Boolean] true if fallback condition is met
|
|
518
524
|
#
|
|
519
|
-
def fallback_condition(status)
|
|
525
|
+
def fallback_condition(status, seconds_in_state)
|
|
520
526
|
interrupted_at_runtime = status.state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED &&
|
|
521
|
-
|
|
527
|
+
seconds_in_state > 60 # 1 minute
|
|
522
528
|
cannot_initialize = status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING &&
|
|
523
|
-
|
|
529
|
+
seconds_in_state > 10 # 10 seconds
|
|
524
530
|
|
|
525
531
|
interrupted_at_runtime || cannot_initialize
|
|
526
532
|
end
|
|
@@ -529,11 +535,12 @@ module LaunchDarkly
|
|
|
529
535
|
# Determine if we should recover to the primary synchronizer.
|
|
530
536
|
#
|
|
531
537
|
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
538
|
+
# @param seconds_in_state [Float] monotonic seconds spent in status.state
|
|
532
539
|
# @return [Boolean] true if recovery condition is met (healthy for too long)
|
|
533
540
|
#
|
|
534
|
-
def recovery_condition(status)
|
|
541
|
+
def recovery_condition(status, seconds_in_state)
|
|
535
542
|
status.state == LaunchDarkly::Interfaces::DataSource::Status::VALID &&
|
|
536
|
-
|
|
543
|
+
seconds_in_state > 300 # 5 minutes
|
|
537
544
|
end
|
|
538
545
|
|
|
539
546
|
#
|
|
@@ -47,6 +47,7 @@ module LaunchDarkly
|
|
|
47
47
|
@stopped = Concurrent::Event.new
|
|
48
48
|
@diagnostic_accumulator = nil
|
|
49
49
|
@connection_attempt_start_time = 0
|
|
50
|
+
@connection_attempt_started_monotonic = nil
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
#
|
|
@@ -97,6 +98,7 @@ module LaunchDarkly
|
|
|
97
98
|
opts = {
|
|
98
99
|
headers: headers,
|
|
99
100
|
read_timeout: STREAM_READ_TIMEOUT,
|
|
101
|
+
connect_timeout: @http_config.connect_timeout,
|
|
100
102
|
logger: @logger,
|
|
101
103
|
socket_factory: @http_config.socket_factory,
|
|
102
104
|
reconnect_time: @initial_reconnect_delay,
|
|
@@ -118,7 +120,7 @@ module LaunchDarkly
|
|
|
118
120
|
next unless update
|
|
119
121
|
|
|
120
122
|
log_connection_result(true)
|
|
121
|
-
@
|
|
123
|
+
@connection_attempt_started_monotonic = nil
|
|
122
124
|
|
|
123
125
|
yield update
|
|
124
126
|
|
|
@@ -366,16 +368,18 @@ module LaunchDarkly
|
|
|
366
368
|
|
|
367
369
|
private def log_connection_started
|
|
368
370
|
@connection_attempt_start_time = Impl::Util.current_time_millis
|
|
371
|
+
@connection_attempt_started_monotonic = Impl::Util.monotonic_seconds
|
|
369
372
|
end
|
|
370
373
|
|
|
371
374
|
private def log_connection_result(is_success)
|
|
372
375
|
return unless @diagnostic_accumulator
|
|
373
|
-
return
|
|
376
|
+
return if @connection_attempt_started_monotonic.nil?
|
|
374
377
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
@connection_attempt_start_time
|
|
378
|
+
# The monotonic stamp is both the "attempt in flight" sentinel and the
|
|
379
|
+
# duration base; the wall-clock value is only the reported timestamp.
|
|
380
|
+
elapsed = ((Impl::Util.monotonic_seconds - @connection_attempt_started_monotonic) * 1_000).to_i
|
|
381
|
+
@diagnostic_accumulator.record_stream_init(@connection_attempt_start_time, !is_success, elapsed)
|
|
382
|
+
@connection_attempt_started_monotonic = nil
|
|
379
383
|
end
|
|
380
384
|
end
|
|
381
385
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "ldclient-rb/impl/util"
|
|
1
2
|
|
|
2
3
|
module LaunchDarkly
|
|
3
4
|
module Impl
|
|
@@ -7,10 +8,17 @@ module LaunchDarkly
|
|
|
7
8
|
# * made thread-safe
|
|
8
9
|
# * removed many unused methods
|
|
9
10
|
# * reading a key does not reset its expiration time, only writing
|
|
11
|
+
# * expiration is measured on a monotonic clock, so wall-clock steps cannot
|
|
12
|
+
# retain entries past their TTL or evict them early
|
|
10
13
|
class ExpiringCache
|
|
11
|
-
|
|
14
|
+
MONOTONIC_CLOCK = -> { Impl::Util.monotonic_seconds }
|
|
15
|
+
private_constant :MONOTONIC_CLOCK
|
|
16
|
+
|
|
17
|
+
# @param clock [#call] returns seconds on a monotonic clock; injectable for tests
|
|
18
|
+
def initialize(max_size, ttl, clock: MONOTONIC_CLOCK)
|
|
12
19
|
@max_size = max_size
|
|
13
20
|
@ttl = ttl
|
|
21
|
+
@clock = clock
|
|
14
22
|
@data_lru = {}
|
|
15
23
|
@data_ttl = {}
|
|
16
24
|
@lock = Mutex.new
|
|
@@ -31,7 +39,7 @@ module LaunchDarkly
|
|
|
31
39
|
@data_ttl.delete(key)
|
|
32
40
|
|
|
33
41
|
@data_lru[key] = val
|
|
34
|
-
@data_ttl[key] =
|
|
42
|
+
@data_ttl[key] = @clock.call
|
|
35
43
|
|
|
36
44
|
if @data_lru.size > @max_size
|
|
37
45
|
key, _ = @data_lru.first # hashes have a FIFO ordering in Ruby
|
|
@@ -63,7 +71,7 @@ module LaunchDarkly
|
|
|
63
71
|
private
|
|
64
72
|
|
|
65
73
|
def ttl_evict
|
|
66
|
-
ttl_horizon =
|
|
74
|
+
ttl_horizon = @clock.call - @ttl
|
|
67
75
|
key, time = @data_ttl.first
|
|
68
76
|
|
|
69
77
|
until time.nil? || time > ttl_horizon
|
|
@@ -68,13 +68,15 @@ module LaunchDarkly
|
|
|
68
68
|
|
|
69
69
|
def get_all_internal(kind)
|
|
70
70
|
items_out = {}
|
|
71
|
-
|
|
72
|
-
(
|
|
71
|
+
prefix = kind_key(kind)
|
|
72
|
+
Diplomat::Kv.get_all(prefix, {}, :return).each do |result|
|
|
73
73
|
value = result[:value]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
next if value.nil?
|
|
75
|
+
db_key = result[:key].to_s
|
|
76
|
+
next unless db_key.start_with?(prefix)
|
|
77
|
+
# Use the key that the item is stored under, not the key inside the item. A deleted
|
|
78
|
+
# item (a "tombstone") is not guaranteed to carry a key of its own.
|
|
79
|
+
items_out[db_key[prefix.length..].to_sym] = Model.deserialize(kind, value)
|
|
78
80
|
end
|
|
79
81
|
items_out
|
|
80
82
|
end
|
|
@@ -113,7 +113,10 @@ module LaunchDarkly
|
|
|
113
113
|
resp = @client.query(req)
|
|
114
114
|
resp.items.each do |item|
|
|
115
115
|
item_out = unmarshal_item(kind, item)
|
|
116
|
-
|
|
116
|
+
next if item_out.nil?
|
|
117
|
+
# Use the sort key that the item is stored under, not the key inside the item. A
|
|
118
|
+
# deleted item (a "tombstone") is not guaranteed to carry a key of its own.
|
|
119
|
+
items_out[item[SORT_KEY].to_sym] = item_out
|
|
117
120
|
end
|
|
118
121
|
break if resp.last_evaluated_key.nil? || resp.last_evaluated_key.length == 0
|
|
119
122
|
req.exclusive_start_key = resp.last_evaluated_key
|
|
@@ -270,7 +270,7 @@ module LaunchDarkly
|
|
|
270
270
|
# @return [LaunchDarkly::Migrations::OperationResult]
|
|
271
271
|
#
|
|
272
272
|
def run()
|
|
273
|
-
start =
|
|
273
|
+
start = Impl::Util.monotonic_seconds
|
|
274
274
|
|
|
275
275
|
begin
|
|
276
276
|
result = @fn.call(@payload)
|
|
@@ -279,7 +279,7 @@ module LaunchDarkly
|
|
|
279
279
|
result = LaunchDarkly::Result.fail("'#{origin}' operation raised an exception", e)
|
|
280
280
|
end
|
|
281
281
|
|
|
282
|
-
@tracker.latency(@origin, (
|
|
282
|
+
@tracker.latency(@origin, (Impl::Util.monotonic_seconds - start) * 1_000) if @measure_latency
|
|
283
283
|
@tracker.error(@origin) if @measure_errors && !result.success?
|
|
284
284
|
@tracker.invoked(@origin)
|
|
285
285
|
|
|
@@ -39,13 +39,15 @@ module LaunchDarkly
|
|
|
39
39
|
@stop_event.wait(@start_delay) unless @start_delay.nil? || @start_delay == 0
|
|
40
40
|
|
|
41
41
|
until @stopped.value do
|
|
42
|
-
started_at =
|
|
42
|
+
started_at = Impl::Util.monotonic_seconds
|
|
43
43
|
begin
|
|
44
44
|
@task.call
|
|
45
45
|
rescue => e
|
|
46
46
|
Impl::Util.log_exception(@logger, "Uncaught exception from repeating task", e)
|
|
47
47
|
end
|
|
48
|
-
|
|
48
|
+
# The run must be measured on the monotonic clock: a wall-clock step would
|
|
49
|
+
# make this wait negative (immediate re-run) or stretch it by the step size.
|
|
50
|
+
delta = @interval - (Impl::Util.monotonic_seconds - started_at)
|
|
49
51
|
@stop_event.wait(delta) if delta > 0
|
|
50
52
|
end
|
|
51
53
|
end
|
|
@@ -23,6 +23,7 @@ module LaunchDarkly
|
|
|
23
23
|
|
|
24
24
|
@mutex = Mutex.new # Covers the following variables
|
|
25
25
|
@last_available = true
|
|
26
|
+
@stopped = false
|
|
26
27
|
# @type [LaunchDarkly::Impl::RepeatingTask, nil]
|
|
27
28
|
@poller = nil
|
|
28
29
|
end
|
|
@@ -53,12 +54,14 @@ module LaunchDarkly
|
|
|
53
54
|
|
|
54
55
|
def stop
|
|
55
56
|
@store.stop
|
|
56
|
-
@mutex.synchronize do
|
|
57
|
-
return if @poller.nil?
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
poller = @mutex.synchronize do
|
|
59
|
+
@stopped = true
|
|
60
|
+
task = @poller
|
|
60
61
|
@poller = nil
|
|
62
|
+
task
|
|
61
63
|
end
|
|
64
|
+
poller&.stop
|
|
62
65
|
end
|
|
63
66
|
|
|
64
67
|
def monitoring_enabled?
|
|
@@ -87,12 +90,12 @@ module LaunchDarkly
|
|
|
87
90
|
@store_update_sink.update_status(status)
|
|
88
91
|
|
|
89
92
|
if available
|
|
90
|
-
@mutex.synchronize do
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@poller.stop
|
|
93
|
+
poller = @mutex.synchronize do
|
|
94
|
+
task = @poller
|
|
94
95
|
@poller = nil
|
|
96
|
+
task
|
|
95
97
|
end
|
|
98
|
+
poller&.stop
|
|
96
99
|
|
|
97
100
|
return
|
|
98
101
|
end
|
|
@@ -102,6 +105,9 @@ module LaunchDarkly
|
|
|
102
105
|
task = Impl::RepeatingTask.new(0.5, 0, -> { self.check_availability }, @logger, 'LD/StoreWrapper#check_availability')
|
|
103
106
|
|
|
104
107
|
@mutex.synchronize do
|
|
108
|
+
# A read can fail after stop, and a poller started then would never be stopped.
|
|
109
|
+
next if @stopped
|
|
110
|
+
|
|
105
111
|
@poller = task
|
|
106
112
|
@poller.start
|
|
107
113
|
end
|
|
@@ -12,6 +12,17 @@ module LaunchDarkly
|
|
|
12
12
|
(Time.now.to_f * 1000).to_i
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
#
|
|
16
|
+
# Seconds on the monotonic clock. Use this for measuring intervals and
|
|
17
|
+
# durations; use the wall clock only for values reported outside the
|
|
18
|
+
# process.
|
|
19
|
+
#
|
|
20
|
+
# @return [Float]
|
|
21
|
+
#
|
|
22
|
+
def self.monotonic_seconds
|
|
23
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
|
|
24
|
+
end
|
|
25
|
+
|
|
15
26
|
def self.default_http_headers(sdk_key, config)
|
|
16
27
|
ret = { "Authorization" => sdk_key, "User-Agent" => "RubyClient/" + LaunchDarkly::VERSION }
|
|
17
28
|
|
data/lib/ldclient-rb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: launchdarkly-server-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.18.
|
|
4
|
+
version: 8.18.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aws-sdk-dynamodb
|