launchdarkly-server-sdk 8.18.1-java → 8.18.2-java
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/data_source/status_provider.rb +35 -2
- data/lib/ldclient-rb/impl/data_source/stream.rb +8 -4
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +13 -9
- data/lib/ldclient-rb/impl/data_system/streaming.rb +9 -6
- data/lib/ldclient-rb/impl/expiring_cache.rb +11 -3
- 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/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: c7079a31a9e896f54f3efa14e977ca139d2bf023458ffaa247b52c0d4216a96e
|
|
4
|
+
data.tar.gz: ecb670e91dcee356c5d8427dddcc071b377cdf5e5d36d965df29a3fc8fd936a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 919d9830ecd32c8e8e0e7656839d240897c94c8059ef9dfffc5769544e5e0c9bd4317ae601d7740713537c302bc16bcbaee1920a60c9b657928fbf68a3fe2af2
|
|
7
|
+
data.tar.gz: a1749cdd89a33e8dacb30efe4d334b072ebded9c5148a2bb49434878f5aa78a634318390a6a5abbc0a132bc0f6da89fb4b0aebea41ee118ffd4ef70657c77c40
|
|
@@ -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?
|
|
@@ -185,13 +186,16 @@ module LaunchDarkly
|
|
|
185
186
|
|
|
186
187
|
def log_connection_started
|
|
187
188
|
@connection_attempt_start_time = Impl::Util::current_time_millis
|
|
189
|
+
@connection_attempt_started_monotonic = Impl::Util.monotonic_seconds
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
def log_connection_result(is_success)
|
|
191
|
-
if !@diagnostic_accumulator.nil? &&
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
195
199
|
end
|
|
196
200
|
end
|
|
197
201
|
end
|
|
@@ -463,10 +463,12 @@ module LaunchDarkly
|
|
|
463
463
|
break if update == "quit"
|
|
464
464
|
|
|
465
465
|
if update == "check"
|
|
466
|
-
# Check condition periodically
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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)
|
|
470
472
|
end
|
|
471
473
|
next
|
|
472
474
|
end
|
|
@@ -517,13 +519,14 @@ module LaunchDarkly
|
|
|
517
519
|
# Determine if we should fallback to the next synchronizer.
|
|
518
520
|
#
|
|
519
521
|
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
522
|
+
# @param seconds_in_state [Float] monotonic seconds spent in status.state
|
|
520
523
|
# @return [Boolean] true if fallback condition is met
|
|
521
524
|
#
|
|
522
|
-
def fallback_condition(status)
|
|
525
|
+
def fallback_condition(status, seconds_in_state)
|
|
523
526
|
interrupted_at_runtime = status.state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED &&
|
|
524
|
-
|
|
527
|
+
seconds_in_state > 60 # 1 minute
|
|
525
528
|
cannot_initialize = status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING &&
|
|
526
|
-
|
|
529
|
+
seconds_in_state > 10 # 10 seconds
|
|
527
530
|
|
|
528
531
|
interrupted_at_runtime || cannot_initialize
|
|
529
532
|
end
|
|
@@ -532,11 +535,12 @@ module LaunchDarkly
|
|
|
532
535
|
# Determine if we should recover to the primary synchronizer.
|
|
533
536
|
#
|
|
534
537
|
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
538
|
+
# @param seconds_in_state [Float] monotonic seconds spent in status.state
|
|
535
539
|
# @return [Boolean] true if recovery condition is met (healthy for too long)
|
|
536
540
|
#
|
|
537
|
-
def recovery_condition(status)
|
|
541
|
+
def recovery_condition(status, seconds_in_state)
|
|
538
542
|
status.state == LaunchDarkly::Interfaces::DataSource::Status::VALID &&
|
|
539
|
-
|
|
543
|
+
seconds_in_state > 300 # 5 minutes
|
|
540
544
|
end
|
|
541
545
|
|
|
542
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
|
#
|
|
@@ -119,7 +120,7 @@ module LaunchDarkly
|
|
|
119
120
|
next unless update
|
|
120
121
|
|
|
121
122
|
log_connection_result(true)
|
|
122
|
-
@
|
|
123
|
+
@connection_attempt_started_monotonic = nil
|
|
123
124
|
|
|
124
125
|
yield update
|
|
125
126
|
|
|
@@ -367,16 +368,18 @@ module LaunchDarkly
|
|
|
367
368
|
|
|
368
369
|
private def log_connection_started
|
|
369
370
|
@connection_attempt_start_time = Impl::Util.current_time_millis
|
|
371
|
+
@connection_attempt_started_monotonic = Impl::Util.monotonic_seconds
|
|
370
372
|
end
|
|
371
373
|
|
|
372
374
|
private def log_connection_result(is_success)
|
|
373
375
|
return unless @diagnostic_accumulator
|
|
374
|
-
return
|
|
376
|
+
return if @connection_attempt_started_monotonic.nil?
|
|
375
377
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
@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
|
|
380
383
|
end
|
|
381
384
|
end
|
|
382
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
|
|
@@ -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
|
|
@@ -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,13 +1,13 @@
|
|
|
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: java
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: aws-sdk-dynamodb
|