Skip to content

Commit afea4af

Browse files
authored
prepare 5.0.0 release (#102)
1 parent c81e795 commit afea4af

23 files changed

+1307
-112
lines changed

.circleci/config.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ workflows:
88
- test-2.2
99
- test-2.3
1010
- test-2.4
11-
- test-jruby-9.1
11+
- test-2.5
12+
- test-jruby-9.2
1213

1314
ruby-docker-template: &ruby-docker-template
1415
steps:
@@ -17,6 +18,7 @@ ruby-docker-template: &ruby-docker-template
1718
if [[ $CIRCLE_JOB == test-jruby* ]]; then
1819
gem install jruby-openssl; # required by bundler, no effect on Ruby MRI
1920
fi
21+
- run: ruby -v
2022
- run: gem install bundler
2123
- run: bundle install
2224
- run: mkdir ./rspec
@@ -40,9 +42,14 @@ jobs:
4042
test-2.4:
4143
<<: *ruby-docker-template
4244
docker:
43-
- image: circleci/ruby:2.4.3-jessie
45+
- image: circleci/ruby:2.4.4-stretch
4446
- image: redis
45-
test-jruby-9.1:
47+
test-2.5:
48+
<<: *ruby-docker-template
49+
docker:
50+
- image: circleci/ruby:2.5.1-stretch
51+
- image: redis
52+
test-jruby-9.2:
4653
<<: *ruby-docker-template
4754
docker:
4855
- image: circleci/jruby:9-jdk
@@ -54,7 +61,7 @@ jobs:
5461
machine:
5562
image: circleci/classic:latest
5663
environment:
57-
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-9.0.5.0"
64+
- RUBIES: "jruby-9.1.17.0"
5865
steps:
5966
- run: sudo apt-get -q update
6067
- run: sudo apt-get -qy install redis-server

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ LaunchDarkly SDK for Ruby
77
[![Test Coverage](https://codeclimate.com/github/launchdarkly/ruby-client/badges/coverage.svg)](https://codeclimate.com/github/launchdarkly/ruby-client/coverage)
88
[![security](https://hakiri.io/github/launchdarkly/ruby-client/master.svg)](https://hakiri.io/github/launchdarkly/ruby-client/master)
99

10+
Supported Ruby versions
11+
-----------------------
12+
13+
This version of the LaunchDarkly SDK has a minimum Ruby version of 2.2.6, or 9.1.6 for JRuby.
14+
1015
Quick setup
1116
-----------
1217

1318
0. Install the Ruby SDK with `gem`
1419

1520
```shell
16-
gem install ldclient-rb --prerelease
21+
gem install ldclient-rb
1722
```
18-
Note: The `--prerelease` flag is there to satisfy the dependency of celluloid 0.18pre which we have tested extensively and have found stable in our use case. Unfortunately, the upstream provider has not promoted this version to stable yet. See [here](https://github.com/celluloid/celluloid/issues/762) This is not required for use in a Gemfile.
1923

2024
1. Require the LaunchDarkly client:
2125

@@ -79,7 +83,7 @@ Note that this gem will automatically switch to using the Rails logger it is det
7983

8084
HTTPS proxy
8185
------------
82-
The Ruby SDK uses Faraday to handle all of its network traffic. Faraday provides built-in support for the use of an HTTPS proxy. If the HTTPS_PROXY environment variable is present then the SDK will proxy all network requests through the URL provided.
86+
The Ruby SDK uses Faraday and Socketry to handle its network traffic. Both of these provide built-in support for the use of an HTTPS proxy. If the HTTPS_PROXY environment variable is present then the SDK will proxy all network requests through the URL provided.
8387

8488
How to set the HTTPS_PROXY environment variable on Mac/Linux systems:
8589
```

ldclient-rb.gemspec

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,18 @@ Gem::Specification.new do |spec|
2626
spec.add_development_dependency "codeclimate-test-reporter", "~> 0"
2727
spec.add_development_dependency "redis", "~> 3.3.5"
2828
spec.add_development_dependency "connection_pool", ">= 2.1.2"
29-
if RUBY_VERSION >= "2.0.0"
30-
spec.add_development_dependency "rake", "~> 10.0"
31-
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
32-
else
33-
spec.add_development_dependency "rake", "12.1.0"
34-
# higher versions of rake fail to install in JRuby 1.7
35-
end
29+
spec.add_development_dependency "rake", "~> 10.0"
30+
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
3631
spec.add_development_dependency "timecop", "~> 0.9.1"
3732

3833
spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
39-
if RUBY_VERSION >= "2.1.0"
40-
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
41-
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
42-
else
43-
spec.add_runtime_dependency "faraday", [">= 0.9", "< 0.14.0"]
44-
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 2"]
45-
end
34+
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
35+
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
4636
spec.add_runtime_dependency "semantic", "~> 1.6.0"
4737
spec.add_runtime_dependency "thread_safe", "~> 0.3"
4838
spec.add_runtime_dependency "net-http-persistent", "~> 2.9"
4939
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0.4"
5040
spec.add_runtime_dependency "hashdiff", "~> 0.2"
51-
spec.add_runtime_dependency "ld-celluloid-eventsource", "~> 0.11.0"
52-
spec.add_runtime_dependency "celluloid", "~> 0.18.0.pre" # transitive dep; specified here for more control
53-
54-
if RUBY_VERSION >= "2.2.2"
55-
spec.add_runtime_dependency "nio4r", "< 3" # for maximum ruby version compatibility.
56-
else
57-
spec.add_runtime_dependency "nio4r", "~> 1.1" # for maximum ruby version compatibility.
58-
end
59-
60-
spec.add_runtime_dependency "waitutil", "0.2"
41+
spec.add_runtime_dependency "http_tools", '~> 0.4.5'
42+
spec.add_runtime_dependency "socketry", "~> 0.5.1"
6143
end

lib/ldclient-rb.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "ldclient-rb/version"
2+
require "ldclient-rb/util"
23
require "ldclient-rb/evaluation"
34
require "ldclient-rb/ldclient"
45
require "ldclient-rb/cache_store"

lib/ldclient-rb/events.rb

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,24 @@ def trigger_flush(buffer, flush_workers)
222222
if !payload.events.empty? || !payload.summary.counters.empty?
223223
# If all available worker threads are busy, success will be false and no job will be queued.
224224
success = flush_workers.post do
225-
resp = EventPayloadSendTask.new.run(@sdk_key, @config, @client, payload, @formatter)
226-
handle_response(resp) if !resp.nil?
225+
begin
226+
resp = EventPayloadSendTask.new.run(@sdk_key, @config, @client, payload, @formatter)
227+
handle_response(resp) if !resp.nil?
228+
rescue => e
229+
@config.logger.warn { "[LDClient] Unexpected error in event processor: #{e.inspect}. \nTrace: #{e.backtrace}" }
230+
end
227231
end
228232
buffer.clear if success # Reset our internal state, these events now belong to the flush worker
229233
end
230234
end
231235

232236
def handle_response(res)
233-
if res.status == 401
234-
@config.logger.error { "[LDClient] Received 401 error, no further events will be posted since SDK key is invalid" }
235-
@disabled.value = true
237+
if res.status >= 400
238+
message = Util.http_error_message(res.status, "event delivery", "some events were dropped")
239+
@config.logger.error { "[LDClient] #{message}" }
240+
if !Util.http_error_recoverable?(res.status)
241+
@disabled.value = true
242+
end
236243
else
237244
if !res.headers.nil? && res.headers.has_key?("Date")
238245
begin
@@ -309,8 +316,7 @@ def run(sdk_key, config, client, payload, formatter)
309316
next
310317
end
311318
if res.status < 200 || res.status >= 300
312-
config.logger.error { "[LDClient] Unexpected status code while processing events: #{res.status}" }
313-
if res.status >= 500
319+
if Util.http_error_recoverable?(res.status)
314320
next
315321
end
316322
end

lib/ldclient-rb/ldclient.rb

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
require "concurrent/atomics"
12
require "digest/sha1"
23
require "logger"
34
require "benchmark"
4-
require "waitutil"
55
require "json"
66
require "openssl"
77

@@ -41,7 +41,9 @@ def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
4141

4242
requestor = Requestor.new(sdk_key, config)
4343

44-
if !@config.offline?
44+
if @config.offline?
45+
@update_processor = NullUpdateProcessor.new
46+
else
4547
if @config.update_processor.nil?
4648
if @config.stream?
4749
@update_processor = StreamProcessor.new(sdk_key, config, requestor)
@@ -53,16 +55,15 @@ def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
5355
else
5456
@update_processor = @config.update_processor
5557
end
56-
@update_processor.start
5758
end
5859

59-
if !@config.offline? && wait_for_sec > 0
60-
begin
61-
WaitUtil.wait_for_condition("LaunchDarkly client initialization", timeout_sec: wait_for_sec, delay_sec: 0.1) do
62-
initialized?
63-
end
64-
rescue WaitUtil::TimeoutError
60+
ready = @update_processor.start
61+
if wait_for_sec > 0
62+
ok = ready.wait(wait_for_sec)
63+
if !ok
6564
@config.logger.error { "[LDClient] Timeout encountered waiting for LaunchDarkly client initialization" }
65+
elsif !@update_processor.initialized?
66+
@config.logger.error { "[LDClient] LaunchDarkly client initialization failed" }
6667
end
6768
end
6869
end
@@ -220,9 +221,7 @@ def all_flags(user)
220221
# @return [void]
221222
def close
222223
@config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
223-
if not @config.offline?
224-
@update_processor.stop
225-
end
224+
@update_processor.stop
226225
@event_processor.stop
227226
@store.stop
228227
end
@@ -255,4 +254,22 @@ def make_feature_event(flag, user, variation, value, default)
255254

256255
private :evaluate, :log_exception, :sanitize_user, :make_feature_event
257256
end
257+
258+
#
259+
# Used internally when the client is offline.
260+
#
261+
class NullUpdateProcessor
262+
def start
263+
e = Concurrent::Event.new
264+
e.set
265+
e
266+
end
267+
268+
def initialized?
269+
true
270+
end
271+
272+
def stop
273+
end
274+
end
258275
end

lib/ldclient-rb/polling.rb

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ def initialize(config, requestor)
99
@initialized = Concurrent::AtomicBoolean.new(false)
1010
@started = Concurrent::AtomicBoolean.new(false)
1111
@stopped = Concurrent::AtomicBoolean.new(false)
12+
@ready = Concurrent::Event.new
1213
end
1314

1415
def initialized?
1516
@initialized.value
1617
end
1718

1819
def start
19-
return unless @started.make_true
20+
return @ready unless @started.make_true
2021
@config.logger.info { "[LDClient] Initializing polling connection" }
2122
create_worker
23+
@ready
2224
end
2325

2426
def stop
@@ -39,6 +41,7 @@ def poll
3941
})
4042
if @initialized.make_true
4143
@config.logger.info { "[LDClient] Polling connection initialized" }
44+
@ready.set
4245
end
4346
end
4447
end
@@ -47,20 +50,24 @@ def create_worker
4750
@worker = Thread.new do
4851
@config.logger.debug { "[LDClient] Starting polling worker" }
4952
while !@stopped.value do
53+
started_at = Time.now
5054
begin
51-
started_at = Time.now
5255
poll
53-
delta = @config.poll_interval - (Time.now - started_at)
54-
if delta > 0
55-
sleep(delta)
56+
rescue UnexpectedResponseError => e
57+
message = Util.http_error_message(e.status, "polling request", "will retry")
58+
@config.logger.error { "[LDClient] #{message}" };
59+
if !Util.http_error_recoverable?(e.status)
60+
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
61+
stop
5662
end
57-
rescue InvalidSDKKeyError
58-
@config.logger.error { "[LDClient] Received 401 error, no further polling requests will be made since SDK key is invalid" };
59-
stop
6063
rescue StandardError => exn
6164
@config.logger.error { "[LDClient] Exception while polling: #{exn.inspect}" }
6265
# TODO: log_exception(__method__.to_s, exn)
6366
end
67+
delta = @config.poll_interval - (Time.now - started_at)
68+
if delta > 0
69+
sleep(delta)
70+
end
6471
end
6572
end
6673
end

lib/ldclient-rb/requestor.rb

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
module LaunchDarkly
66

7-
class InvalidSDKKeyError < StandardError
7+
class UnexpectedResponseError < StandardError
8+
def initialize(status)
9+
@status = status
10+
end
11+
12+
def status
13+
@status
14+
end
815
end
916

1017
class Requestor
@@ -13,7 +20,7 @@ def initialize(sdk_key, config)
1320
@config = config
1421
@client = Faraday.new do |builder|
1522
builder.use :http_cache, store: @config.cache_store
16-
23+
1724
builder.adapter :net_http_persistent
1825
end
1926
end
@@ -44,19 +51,8 @@ def make_request(path)
4451

4552
@config.logger.debug { "[LDClient] Got response from uri: #{uri}\n\tstatus code: #{res.status}\n\theaders: #{res.headers}\n\tbody: #{res.body}" }
4653

47-
if res.status == 401
48-
@config.logger.error { "[LDClient] Invalid SDK key" }
49-
raise InvalidSDKKeyError
50-
end
51-
52-
if res.status == 404
53-
@config.logger.error { "[LDClient] Resource not found" }
54-
return nil
55-
end
56-
5754
if res.status < 200 || res.status >= 300
58-
@config.logger.error { "[LDClient] Unexpected status code #{res.status}" }
59-
return nil
55+
raise UnexpectedResponseError.new(res.status)
6056
end
6157

6258
JSON.parse(res.body, symbolize_names: true)

0 commit comments

Comments
 (0)