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.
@@ -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
- @task.stop
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
- @config.feature_store.init(all_data)
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
- unless Util.http_error_recoverable?(e.status)
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
- stop
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
@@ -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
- unless Util.http_error_recoverable?(status)
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
- stop
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
- private
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
- if method == PUT
90
- message = JSON.parse(message.data, symbolize_names: true)
91
- all_data = Impl::Model.make_all_store_data(message[:data], @config.logger)
92
- @feature_store.init(all_data)
93
- @initialized.make_true
94
- @config.logger.info { "[LDClient] Stream initialized" }
95
- @ready.set
96
- elsif method == PATCH
97
- data = JSON.parse(message.data, symbolize_names: true)
98
- for kind in [FEATURES, SEGMENTS]
99
- key = key_for_path(kind, data[:path])
100
- if key
101
- item = Impl::Model.deserialize(kind, data[:data], @config.logger)
102
- @feature_store.upsert(kind, item)
103
- break
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
- end
106
- elsif method == DELETE
107
- data = JSON.parse(message.data, symbolize_names: true)
108
- for kind in [FEATURES, SEGMENTS]
109
- key = key_for_path(kind, data[:path])
110
- if key
111
- @feature_store.delete(kind, key, data[:version])
112
- break
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
- else
116
- @config.logger.warn { "[LDClient] Unknown message received: #{method}" }
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
 
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "7.1.0"
2
+ VERSION = "7.3.0"
3
3
  end
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.1.0
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-04-13 00:00:00.000000000 Z
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.12
360
+ rubygems_version: 3.4.20
342
361
  signing_key:
343
362
  specification_version: 4
344
363
  summary: LaunchDarkly SDK for Ruby