launchdarkly-server-sdk 5.5.11 → 5.5.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/ldclient-rb/events.rb +62 -25
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6711ad265d73300d06cb1a287c76ae1981e9c1e7
|
4
|
+
data.tar.gz: 27c72e98745eb679e91a0691766991277490995f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f0a6c09dd0cff741849392b32d75ac469a08116d918ebaeff2020693d7971bdf5082fee42fe310298fdfb20051da3547e98964fec88c952a3351d1642b5a4c8
|
7
|
+
data.tar.gz: 4f4474d63efe13be2db68977b51b1f7d65217415f740823e439568980c28713e0bbdcaab83a7e23c32d0889eed32fcd6cf0778d8dd1e15e0d573994081219157
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
## [5.5.12] - 2019-08-05
|
6
|
+
### Fixed:
|
7
|
+
- Under conditions where analytics events are being generated at an extremely high rate (for instance, if an application is evaluating a flag repeatedly in a tight loop on many threads), it was possible for the internal event processing logic to fall behind on processing the events, causing them to use more and more memory. The logic has been changed to drop events if necessary so that besides the existing limit on the number of events waiting to be sent to LaunchDarkly (`config.capacity`), the same limit also applies on the number of events that are waiting to be processed by the worker thread that decides whether or not to send them to LaunchDarkly. If that limit is exceeded, this warning message will be logged once: "Events are being produced faster than they can be processed; some events will be dropped". Under normal conditions this should never happen; this change is meant to avoid a concurrency bottleneck in applications that are already so busy that thread starvation is likely.
|
8
|
+
|
5
9
|
## [5.5.11] - 2019-07-24
|
6
10
|
### Fixed:
|
7
11
|
- `FileDataSource` was using `YAML.load`, which has a known [security vulnerability](https://trailofbits.github.io/rubysec/yaml/index.html). This has been changed to use `YAML.safe_load`, which will refuse to parse any files that contain the `!` directives used in this type of attack. This issue does not affect any applications that do not use `FileDataSource` (which is meant for testing purposes, not production use). ([#139](https://github.com/launchdarkly/ruby-server-sdk/issues/139))
|
data/lib/ldclient-rb/events.rb
CHANGED
@@ -4,6 +4,23 @@ require "concurrent/executors"
|
|
4
4
|
require "thread"
|
5
5
|
require "time"
|
6
6
|
|
7
|
+
#
|
8
|
+
# Analytics event processing in the SDK involves several components. The purpose of this design is to
|
9
|
+
# minimize overhead on the application threads that are generating analytics events.
|
10
|
+
#
|
11
|
+
# EventProcessor receives an analytics event from the SDK client, on an application thread. It places
|
12
|
+
# the event in a bounded queue, the "inbox", and immediately returns.
|
13
|
+
#
|
14
|
+
# On a separate worker thread, EventDispatcher consumes events from the inbox. These are considered
|
15
|
+
# "input events" because they may or may not actually be sent to LaunchDarkly; most flag evaluation
|
16
|
+
# events are not sent, but are counted and the counters become part of a single summary event.
|
17
|
+
# EventDispatcher updates those counters, creates "index" events for any users that have not been seen
|
18
|
+
# recently, and places any events that will be sent to LaunchDarkly into the "outbox" queue.
|
19
|
+
#
|
20
|
+
# When it is time to flush events to LaunchDarkly, the contents of the outbox are handed off to
|
21
|
+
# another worker thread which sends the HTTP request.
|
22
|
+
#
|
23
|
+
|
7
24
|
module LaunchDarkly
|
8
25
|
MAX_FLUSH_WORKERS = 5
|
9
26
|
CURRENT_SCHEMA_VERSION = 3
|
@@ -68,28 +85,30 @@ module LaunchDarkly
|
|
68
85
|
# @private
|
69
86
|
class EventProcessor
|
70
87
|
def initialize(sdk_key, config, client = nil)
|
71
|
-
@
|
88
|
+
@logger = config.logger
|
89
|
+
@inbox = SizedQueue.new(config.capacity)
|
72
90
|
@flush_task = Concurrent::TimerTask.new(execution_interval: config.flush_interval) do
|
73
|
-
|
91
|
+
post_to_inbox(FlushMessage.new)
|
74
92
|
end
|
75
93
|
@flush_task.execute
|
76
94
|
@users_flush_task = Concurrent::TimerTask.new(execution_interval: config.user_keys_flush_interval) do
|
77
|
-
|
95
|
+
post_to_inbox(FlushUsersMessage.new)
|
78
96
|
end
|
79
97
|
@users_flush_task.execute
|
80
98
|
@stopped = Concurrent::AtomicBoolean.new(false)
|
81
|
-
|
82
|
-
|
99
|
+
@inbox_full = Concurrent::AtomicBoolean.new(false)
|
100
|
+
|
101
|
+
EventDispatcher.new(@inbox, sdk_key, config, client)
|
83
102
|
end
|
84
103
|
|
85
104
|
def add_event(event)
|
86
105
|
event[:creationDate] = (Time.now.to_f * 1000).to_i
|
87
|
-
|
106
|
+
post_to_inbox(EventMessage.new(event))
|
88
107
|
end
|
89
108
|
|
90
109
|
def flush
|
91
110
|
# flush is done asynchronously
|
92
|
-
|
111
|
+
post_to_inbox(FlushMessage.new)
|
93
112
|
end
|
94
113
|
|
95
114
|
def stop
|
@@ -97,9 +116,11 @@ module LaunchDarkly
|
|
97
116
|
if @stopped.make_true
|
98
117
|
@flush_task.shutdown
|
99
118
|
@users_flush_task.shutdown
|
100
|
-
|
119
|
+
# Note that here we are not calling post_to_inbox, because we *do* want to wait if the inbox
|
120
|
+
# is full; an orderly shutdown can't happen unless these messages are received.
|
121
|
+
@inbox << FlushMessage.new
|
101
122
|
stop_msg = StopMessage.new
|
102
|
-
@
|
123
|
+
@inbox << stop_msg
|
103
124
|
stop_msg.wait_for_completion
|
104
125
|
end
|
105
126
|
end
|
@@ -107,14 +128,30 @@ module LaunchDarkly
|
|
107
128
|
# exposed only for testing
|
108
129
|
def wait_until_inactive
|
109
130
|
sync_msg = TestSyncMessage.new
|
110
|
-
@
|
131
|
+
@inbox << sync_msg
|
111
132
|
sync_msg.wait_for_completion
|
112
133
|
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def post_to_inbox(message)
|
138
|
+
begin
|
139
|
+
@inbox.push(message, non_block=true)
|
140
|
+
rescue ThreadError
|
141
|
+
# If the inbox is full, it means the EventDispatcher thread is seriously backed up with not-yet-processed
|
142
|
+
# events. This is unlikely, but if it happens, it means the application is probably doing a ton of flag
|
143
|
+
# evaluations across many threads-- so if we wait for a space in the inbox, we risk a very serious slowdown
|
144
|
+
# of the app. To avoid that, we'll just drop the event. The log warning about this will only be shown once.
|
145
|
+
if @inbox_full.make_true
|
146
|
+
@logger.warn { "[LDClient] Events are being produced faster than they can be processed; some events will be dropped" }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
113
150
|
end
|
114
151
|
|
115
152
|
# @private
|
116
153
|
class EventDispatcher
|
117
|
-
def initialize(
|
154
|
+
def initialize(inbox, sdk_key, config, client)
|
118
155
|
@sdk_key = sdk_key
|
119
156
|
@config = config
|
120
157
|
|
@@ -129,10 +166,10 @@ module LaunchDarkly
|
|
129
166
|
@disabled = Concurrent::AtomicBoolean.new(false)
|
130
167
|
@last_known_past_time = Concurrent::AtomicReference.new(0)
|
131
168
|
|
132
|
-
|
169
|
+
outbox = EventBuffer.new(config.capacity, config.logger)
|
133
170
|
flush_workers = NonBlockingThreadPool.new(MAX_FLUSH_WORKERS)
|
134
171
|
|
135
|
-
Thread.new { main_loop(
|
172
|
+
Thread.new { main_loop(inbox, outbox, flush_workers) }
|
136
173
|
end
|
137
174
|
|
138
175
|
private
|
@@ -141,16 +178,16 @@ module LaunchDarkly
|
|
141
178
|
(Time.now.to_f * 1000).to_i
|
142
179
|
end
|
143
180
|
|
144
|
-
def main_loop(
|
181
|
+
def main_loop(inbox, outbox, flush_workers)
|
145
182
|
running = true
|
146
183
|
while running do
|
147
184
|
begin
|
148
|
-
message =
|
185
|
+
message = inbox.pop
|
149
186
|
case message
|
150
187
|
when EventMessage
|
151
|
-
dispatch_event(message.event,
|
188
|
+
dispatch_event(message.event, outbox)
|
152
189
|
when FlushMessage
|
153
|
-
trigger_flush(
|
190
|
+
trigger_flush(outbox, flush_workers)
|
154
191
|
when FlushUsersMessage
|
155
192
|
@user_keys.clear
|
156
193
|
when TestSyncMessage
|
@@ -181,11 +218,11 @@ module LaunchDarkly
|
|
181
218
|
flush_workers.wait_all
|
182
219
|
end
|
183
220
|
|
184
|
-
def dispatch_event(event,
|
221
|
+
def dispatch_event(event, outbox)
|
185
222
|
return if @disabled.value
|
186
223
|
|
187
224
|
# Always record the event in the summary.
|
188
|
-
|
225
|
+
outbox.add_to_summary(event)
|
189
226
|
|
190
227
|
# Decide whether to add the event to the payload. Feature events may be added twice, once for
|
191
228
|
# the event (if tracked) and once for debugging.
|
@@ -205,7 +242,7 @@ module LaunchDarkly
|
|
205
242
|
# an identify event for that user.
|
206
243
|
if !(will_add_full_event && @config.inline_users_in_events)
|
207
244
|
if event.has_key?(:user) && !notice_user(event[:user]) && event[:kind] != "identify"
|
208
|
-
|
245
|
+
outbox.add_event({
|
209
246
|
kind: "index",
|
210
247
|
creationDate: event[:creationDate],
|
211
248
|
user: event[:user]
|
@@ -213,8 +250,8 @@ module LaunchDarkly
|
|
213
250
|
end
|
214
251
|
end
|
215
252
|
|
216
|
-
|
217
|
-
|
253
|
+
outbox.add_event(event) if will_add_full_event
|
254
|
+
outbox.add_event(debug_event) if !debug_event.nil?
|
218
255
|
end
|
219
256
|
|
220
257
|
# Add to the set of users we've noticed, and return true if the user was already known to us.
|
@@ -236,12 +273,12 @@ module LaunchDarkly
|
|
236
273
|
end
|
237
274
|
end
|
238
275
|
|
239
|
-
def trigger_flush(
|
276
|
+
def trigger_flush(outbox, flush_workers)
|
240
277
|
if @disabled.value
|
241
278
|
return
|
242
279
|
end
|
243
280
|
|
244
|
-
payload =
|
281
|
+
payload = outbox.get_payload
|
245
282
|
if !payload.events.empty? || !payload.summary.counters.empty?
|
246
283
|
# If all available worker threads are busy, success will be false and no job will be queued.
|
247
284
|
success = flush_workers.post do
|
@@ -252,7 +289,7 @@ module LaunchDarkly
|
|
252
289
|
Util.log_exception(@config.logger, "Unexpected error in event processor", e)
|
253
290
|
end
|
254
291
|
end
|
255
|
-
|
292
|
+
outbox.clear if success # Reset our internal state, these events now belong to the flush worker
|
256
293
|
end
|
257
294
|
end
|
258
295
|
|
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: 5.5.
|
4
|
+
version: 5.5.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-dynamodb
|