launchdarkly-server-sdk 7.1.0 → 7.3.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/README.md +2 -2
- data/lib/ldclient-rb/config.rb +16 -1
- data/lib/ldclient-rb/context.rb +4 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +3 -3
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +59 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/event_sender.rb +1 -0
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +18 -1
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/in_memory_store.rb +7 -0
- data/lib/ldclient-rb/integrations/file_data.rb +1 -1
- data/lib/ldclient-rb/integrations/test_data.rb +3 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
- data/lib/ldclient-rb/interfaces.rb +489 -0
- data/lib/ldclient-rb/ldclient.rb +65 -3
- data/lib/ldclient-rb/polling.rb +51 -5
- data/lib/ldclient-rb/stream.rb +88 -28
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +22 -3
data/lib/ldclient-rb/polling.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "ldclient-rb/impl/repeating_task"
|
2
2
|
|
3
3
|
require "concurrent/atomics"
|
4
|
+
require "json"
|
4
5
|
require "thread"
|
5
6
|
|
6
7
|
module LaunchDarkly
|
@@ -27,30 +28,75 @@ module LaunchDarkly
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def stop
|
30
|
-
|
31
|
-
@config.logger.info { "[LDClient] Polling connection stopped" }
|
31
|
+
stop_with_error_info
|
32
32
|
end
|
33
33
|
|
34
34
|
def poll
|
35
35
|
begin
|
36
36
|
all_data = @requestor.request_all_data
|
37
37
|
if all_data
|
38
|
-
|
38
|
+
update_sink_or_data_store.init(all_data)
|
39
39
|
if @initialized.make_true
|
40
40
|
@config.logger.info { "[LDClient] Polling connection initialized" }
|
41
41
|
@ready.set
|
42
42
|
end
|
43
43
|
end
|
44
|
+
@config.data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::VALID, nil)
|
45
|
+
rescue JSON::ParserError => e
|
46
|
+
@config.logger.error { "[LDClient] JSON parsing failed for polling response." }
|
47
|
+
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
48
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::INVALID_DATA,
|
49
|
+
0,
|
50
|
+
e.to_s,
|
51
|
+
Time.now
|
52
|
+
)
|
53
|
+
@config.data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED, error_info)
|
44
54
|
rescue UnexpectedResponseError => e
|
55
|
+
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
56
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::ERROR_RESPONSE, e.status, nil, Time.now)
|
45
57
|
message = Util.http_error_message(e.status, "polling request", "will retry")
|
46
58
|
@config.logger.error { "[LDClient] #{message}" }
|
47
|
-
|
59
|
+
|
60
|
+
if Util.http_error_recoverable?(e.status)
|
61
|
+
@config.data_source_update_sink&.update_status(
|
62
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
63
|
+
error_info
|
64
|
+
)
|
65
|
+
else
|
48
66
|
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
49
|
-
|
67
|
+
stop_with_error_info error_info
|
50
68
|
end
|
51
69
|
rescue StandardError => e
|
52
70
|
Util.log_exception(@config.logger, "Exception while polling", e)
|
71
|
+
@config.data_source_update_sink&.update_status(
|
72
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
73
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::UNKNOWN, 0, e.to_s, Time.now)
|
74
|
+
)
|
53
75
|
end
|
54
76
|
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# The original implementation of this class relied on the feature store
|
80
|
+
# directly, which we are trying to move away from. Customers who might have
|
81
|
+
# instantiated this directly for some reason wouldn't know they have to set
|
82
|
+
# the config's sink manually, so we have to fall back to the store if the
|
83
|
+
# sink isn't present.
|
84
|
+
#
|
85
|
+
# The next major release should be able to simplify this structure and
|
86
|
+
# remove the need for fall back to the data store because the update sink
|
87
|
+
# should always be present.
|
88
|
+
#
|
89
|
+
private def update_sink_or_data_store
|
90
|
+
@config.data_source_update_sink || @config.feature_store
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# @param [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] error_info
|
95
|
+
#
|
96
|
+
private def stop_with_error_info(error_info = nil)
|
97
|
+
@task.stop
|
98
|
+
@config.logger.info { "[LDClient] Polling connection stopped" }
|
99
|
+
@config.data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::OFF, error_info)
|
100
|
+
end
|
55
101
|
end
|
56
102
|
end
|
data/lib/ldclient-rb/stream.rb
CHANGED
@@ -25,6 +25,7 @@ module LaunchDarkly
|
|
25
25
|
def initialize(sdk_key, config, diagnostic_accumulator = nil)
|
26
26
|
@sdk_key = sdk_key
|
27
27
|
@config = config
|
28
|
+
@data_source_update_sink = config.data_source_update_sink
|
28
29
|
@feature_store = config.feature_store
|
29
30
|
@initialized = Concurrent::AtomicBoolean.new(false)
|
30
31
|
@started = Concurrent::AtomicBoolean.new(false)
|
@@ -60,12 +61,31 @@ module LaunchDarkly
|
|
60
61
|
case err
|
61
62
|
when SSE::Errors::HTTPStatusError
|
62
63
|
status = err.status
|
64
|
+
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
65
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::ERROR_RESPONSE, status, nil, Time.now)
|
63
66
|
message = Util.http_error_message(status, "streaming connection", "will retry")
|
64
67
|
@config.logger.error { "[LDClient] #{message}" }
|
65
|
-
|
68
|
+
|
69
|
+
if Util.http_error_recoverable?(status)
|
70
|
+
@data_source_update_sink&.update_status(
|
71
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
72
|
+
error_info
|
73
|
+
)
|
74
|
+
else
|
66
75
|
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
67
|
-
|
76
|
+
stop_with_error_info error_info
|
68
77
|
end
|
78
|
+
when SSE::Errors::HTTPContentTypeError, SSE::Errors::HTTPProxyError, SSE::Errors::ReadTimeoutError
|
79
|
+
@data_source_update_sink&.update_status(
|
80
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
81
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::NETWORK_ERROR, 0, err.to_s, Time.now)
|
82
|
+
)
|
83
|
+
|
84
|
+
else
|
85
|
+
@data_source_update_sink&.update_status(
|
86
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
87
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::UNKNOWN, 0, err.to_s, Time.now)
|
88
|
+
)
|
69
89
|
end
|
70
90
|
}
|
71
91
|
end
|
@@ -74,46 +94,86 @@ module LaunchDarkly
|
|
74
94
|
end
|
75
95
|
|
76
96
|
def stop
|
97
|
+
stop_with_error_info
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
#
|
103
|
+
# @param [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] error_info
|
104
|
+
#
|
105
|
+
def stop_with_error_info(error_info = nil)
|
77
106
|
if @stopped.make_true
|
78
107
|
@es.close
|
108
|
+
@data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::OFF, error_info)
|
79
109
|
@config.logger.info { "[LDClient] Stream connection stopped" }
|
80
110
|
end
|
81
111
|
end
|
82
112
|
|
83
|
-
|
113
|
+
#
|
114
|
+
# The original implementation of this class relied on the feature store
|
115
|
+
# directly, which we are trying to move away from. Customers who might have
|
116
|
+
# instantiated this directly for some reason wouldn't know they have to set
|
117
|
+
# the config's sink manually, so we have to fall back to the store if the
|
118
|
+
# sink isn't present.
|
119
|
+
#
|
120
|
+
# The next major release should be able to simplify this structure and
|
121
|
+
# remove the need for fall back to the data store because the update sink
|
122
|
+
# should always be present.
|
123
|
+
#
|
124
|
+
def update_sink_or_data_store
|
125
|
+
@data_source_update_sink || @feature_store
|
126
|
+
end
|
84
127
|
|
85
128
|
def process_message(message)
|
86
129
|
log_connection_result(true)
|
87
130
|
method = message.type
|
88
131
|
@config.logger.debug { "[LDClient] Stream received #{method} message: #{message.data}" }
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
132
|
+
|
133
|
+
begin
|
134
|
+
if method == PUT
|
135
|
+
message = JSON.parse(message.data, symbolize_names: true)
|
136
|
+
all_data = Impl::Model.make_all_store_data(message[:data], @config.logger)
|
137
|
+
update_sink_or_data_store.init(all_data)
|
138
|
+
@initialized.make_true
|
139
|
+
@config.logger.info { "[LDClient] Stream initialized" }
|
140
|
+
@ready.set
|
141
|
+
elsif method == PATCH
|
142
|
+
data = JSON.parse(message.data, symbolize_names: true)
|
143
|
+
for kind in [FEATURES, SEGMENTS]
|
144
|
+
key = key_for_path(kind, data[:path])
|
145
|
+
if key
|
146
|
+
item = Impl::Model.deserialize(kind, data[:data], @config.logger)
|
147
|
+
update_sink_or_data_store.upsert(kind, item)
|
148
|
+
break
|
149
|
+
end
|
104
150
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
151
|
+
elsif method == DELETE
|
152
|
+
data = JSON.parse(message.data, symbolize_names: true)
|
153
|
+
for kind in [FEATURES, SEGMENTS]
|
154
|
+
key = key_for_path(kind, data[:path])
|
155
|
+
if key
|
156
|
+
update_sink_or_data_store.delete(kind, key, data[:version])
|
157
|
+
break
|
158
|
+
end
|
113
159
|
end
|
160
|
+
else
|
161
|
+
@config.logger.warn { "[LDClient] Unknown message received: #{method}" }
|
114
162
|
end
|
115
|
-
|
116
|
-
@
|
163
|
+
|
164
|
+
@data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::VALID, nil)
|
165
|
+
rescue JSON::ParserError => e
|
166
|
+
@config.logger.error { "[LDClient] JSON parsing failed for method #{method}. Ignoring event." }
|
167
|
+
error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
168
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::INVALID_DATA,
|
169
|
+
0,
|
170
|
+
e.to_s,
|
171
|
+
Time.now
|
172
|
+
)
|
173
|
+
@data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED, error_info)
|
174
|
+
|
175
|
+
# Re-raise the exception so the SSE implementation can catch it and restart the stream.
|
176
|
+
raise
|
117
177
|
end
|
118
178
|
end
|
119
179
|
|
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: 7.
|
4
|
+
version: 7.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-dynamodb
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 2.2.33
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.21'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.21'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -274,8 +288,12 @@ files:
|
|
274
288
|
- lib/ldclient-rb/flags_state.rb
|
275
289
|
- lib/ldclient-rb/impl.rb
|
276
290
|
- lib/ldclient-rb/impl/big_segments.rb
|
291
|
+
- lib/ldclient-rb/impl/broadcaster.rb
|
277
292
|
- lib/ldclient-rb/impl/context.rb
|
278
293
|
- lib/ldclient-rb/impl/context_filter.rb
|
294
|
+
- lib/ldclient-rb/impl/data_source.rb
|
295
|
+
- lib/ldclient-rb/impl/data_store.rb
|
296
|
+
- lib/ldclient-rb/impl/dependency_tracker.rb
|
279
297
|
- lib/ldclient-rb/impl/diagnostic_events.rb
|
280
298
|
- lib/ldclient-rb/impl/evaluator.rb
|
281
299
|
- lib/ldclient-rb/impl/evaluator_bucketing.rb
|
@@ -284,6 +302,7 @@ files:
|
|
284
302
|
- lib/ldclient-rb/impl/event_sender.rb
|
285
303
|
- lib/ldclient-rb/impl/event_summarizer.rb
|
286
304
|
- lib/ldclient-rb/impl/event_types.rb
|
305
|
+
- lib/ldclient-rb/impl/flag_tracker.rb
|
287
306
|
- lib/ldclient-rb/impl/integrations/consul_impl.rb
|
288
307
|
- lib/ldclient-rb/impl/integrations/dynamodb_impl.rb
|
289
308
|
- lib/ldclient-rb/impl/integrations/file_data_source.rb
|
@@ -338,7 +357,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
338
357
|
- !ruby/object:Gem::Version
|
339
358
|
version: '0'
|
340
359
|
requirements: []
|
341
|
-
rubygems_version: 3.4.
|
360
|
+
rubygems_version: 3.4.20
|
342
361
|
signing_key:
|
343
362
|
specification_version: 4
|
344
363
|
summary: LaunchDarkly SDK for Ruby
|