Skip to content

Commit

Permalink
prepare 5.0.0 release (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Jun 26, 2018
1 parent c81e795 commit afea4af
Show file tree
Hide file tree
Showing 23 changed files with 1,307 additions and 112 deletions.
15 changes: 11 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ workflows:
- test-2.2
- test-2.3
- test-2.4
- test-jruby-9.1
- test-2.5
- test-jruby-9.2

ruby-docker-template: &ruby-docker-template
steps:
Expand All @@ -17,6 +18,7 @@ ruby-docker-template: &ruby-docker-template
if [[ $CIRCLE_JOB == test-jruby* ]]; then
gem install jruby-openssl; # required by bundler, no effect on Ruby MRI
fi
- run: ruby -v
- run: gem install bundler
- run: bundle install
- run: mkdir ./rspec
Expand All @@ -40,9 +42,14 @@ jobs:
test-2.4:
<<: *ruby-docker-template
docker:
- image: circleci/ruby:2.4.3-jessie
- image: circleci/ruby:2.4.4-stretch
- image: redis
test-jruby-9.1:
test-2.5:
<<: *ruby-docker-template
docker:
- image: circleci/ruby:2.5.1-stretch
- image: redis
test-jruby-9.2:
<<: *ruby-docker-template
docker:
- image: circleci/jruby:9-jdk
Expand All @@ -54,7 +61,7 @@ jobs:
machine:
image: circleci/classic:latest
environment:
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-9.0.5.0"
- RUBIES: "jruby-9.1.17.0"
steps:
- run: sudo apt-get -q update
- run: sudo apt-get -qy install redis-server
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ LaunchDarkly SDK for Ruby
[![Test Coverage](https://codeclimate.com/github/launchdarkly/ruby-client/badges/coverage.svg)](https://codeclimate.com/github/launchdarkly/ruby-client/coverage)
[![security](https://hakiri.io/github/launchdarkly/ruby-client/master.svg)](https://hakiri.io/github/launchdarkly/ruby-client/master)

Supported Ruby versions
-----------------------

This version of the LaunchDarkly SDK has a minimum Ruby version of 2.2.6, or 9.1.6 for JRuby.

Quick setup
-----------

0. Install the Ruby SDK with `gem`

```shell
gem install ldclient-rb --prerelease
gem install ldclient-rb
```
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.

1. Require the LaunchDarkly client:

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

HTTPS proxy
------------
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.
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.

How to set the HTTPS_PROXY environment variable on Mac/Linux systems:
```
Expand Down
30 changes: 6 additions & 24 deletions ldclient-rb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,18 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "codeclimate-test-reporter", "~> 0"
spec.add_development_dependency "redis", "~> 3.3.5"
spec.add_development_dependency "connection_pool", ">= 2.1.2"
if RUBY_VERSION >= "2.0.0"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
else
spec.add_development_dependency "rake", "12.1.0"
# higher versions of rake fail to install in JRuby 1.7
end
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
spec.add_development_dependency "timecop", "~> 0.9.1"

spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
if RUBY_VERSION >= "2.1.0"
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
else
spec.add_runtime_dependency "faraday", [">= 0.9", "< 0.14.0"]
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 2"]
end
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
spec.add_runtime_dependency "semantic", "~> 1.6.0"
spec.add_runtime_dependency "thread_safe", "~> 0.3"
spec.add_runtime_dependency "net-http-persistent", "~> 2.9"
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0.4"
spec.add_runtime_dependency "hashdiff", "~> 0.2"
spec.add_runtime_dependency "ld-celluloid-eventsource", "~> 0.11.0"
spec.add_runtime_dependency "celluloid", "~> 0.18.0.pre" # transitive dep; specified here for more control

if RUBY_VERSION >= "2.2.2"
spec.add_runtime_dependency "nio4r", "< 3" # for maximum ruby version compatibility.
else
spec.add_runtime_dependency "nio4r", "~> 1.1" # for maximum ruby version compatibility.
end

spec.add_runtime_dependency "waitutil", "0.2"
spec.add_runtime_dependency "http_tools", '~> 0.4.5'
spec.add_runtime_dependency "socketry", "~> 0.5.1"
end
1 change: 1 addition & 0 deletions lib/ldclient-rb.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "ldclient-rb/version"
require "ldclient-rb/util"
require "ldclient-rb/evaluation"
require "ldclient-rb/ldclient"
require "ldclient-rb/cache_store"
Expand Down
20 changes: 13 additions & 7 deletions lib/ldclient-rb/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,24 @@ def trigger_flush(buffer, flush_workers)
if !payload.events.empty? || !payload.summary.counters.empty?
# If all available worker threads are busy, success will be false and no job will be queued.
success = flush_workers.post do
resp = EventPayloadSendTask.new.run(@sdk_key, @config, @client, payload, @formatter)
handle_response(resp) if !resp.nil?
begin
resp = EventPayloadSendTask.new.run(@sdk_key, @config, @client, payload, @formatter)
handle_response(resp) if !resp.nil?
rescue => e
@config.logger.warn { "[LDClient] Unexpected error in event processor: #{e.inspect}. \nTrace: #{e.backtrace}" }
end
end
buffer.clear if success # Reset our internal state, these events now belong to the flush worker
end
end

def handle_response(res)
if res.status == 401
@config.logger.error { "[LDClient] Received 401 error, no further events will be posted since SDK key is invalid" }
@disabled.value = true
if res.status >= 400
message = Util.http_error_message(res.status, "event delivery", "some events were dropped")
@config.logger.error { "[LDClient] #{message}" }
if !Util.http_error_recoverable?(res.status)
@disabled.value = true
end
else
if !res.headers.nil? && res.headers.has_key?("Date")
begin
Expand Down Expand Up @@ -309,8 +316,7 @@ def run(sdk_key, config, client, payload, formatter)
next
end
if res.status < 200 || res.status >= 300
config.logger.error { "[LDClient] Unexpected status code while processing events: #{res.status}" }
if res.status >= 500
if Util.http_error_recoverable?(res.status)
next
end
end
Expand Down
41 changes: 29 additions & 12 deletions lib/ldclient-rb/ldclient.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "concurrent/atomics"
require "digest/sha1"
require "logger"
require "benchmark"
require "waitutil"
require "json"
require "openssl"

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

requestor = Requestor.new(sdk_key, config)

if !@config.offline?
if @config.offline?
@update_processor = NullUpdateProcessor.new
else
if @config.update_processor.nil?
if @config.stream?
@update_processor = StreamProcessor.new(sdk_key, config, requestor)
Expand All @@ -53,16 +55,15 @@ def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
else
@update_processor = @config.update_processor
end
@update_processor.start
end

if !@config.offline? && wait_for_sec > 0
begin
WaitUtil.wait_for_condition("LaunchDarkly client initialization", timeout_sec: wait_for_sec, delay_sec: 0.1) do
initialized?
end
rescue WaitUtil::TimeoutError
ready = @update_processor.start
if wait_for_sec > 0
ok = ready.wait(wait_for_sec)
if !ok
@config.logger.error { "[LDClient] Timeout encountered waiting for LaunchDarkly client initialization" }
elsif !@update_processor.initialized?
@config.logger.error { "[LDClient] LaunchDarkly client initialization failed" }
end
end
end
Expand Down Expand Up @@ -220,9 +221,7 @@ def all_flags(user)
# @return [void]
def close
@config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
if not @config.offline?
@update_processor.stop
end
@update_processor.stop
@event_processor.stop
@store.stop
end
Expand Down Expand Up @@ -255,4 +254,22 @@ def make_feature_event(flag, user, variation, value, default)

private :evaluate, :log_exception, :sanitize_user, :make_feature_event
end

#
# Used internally when the client is offline.
#
class NullUpdateProcessor
def start
e = Concurrent::Event.new
e.set
e
end

def initialized?
true
end

def stop
end
end
end
23 changes: 15 additions & 8 deletions lib/ldclient-rb/polling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ def initialize(config, requestor)
@initialized = Concurrent::AtomicBoolean.new(false)
@started = Concurrent::AtomicBoolean.new(false)
@stopped = Concurrent::AtomicBoolean.new(false)
@ready = Concurrent::Event.new
end

def initialized?
@initialized.value
end

def start
return unless @started.make_true
return @ready unless @started.make_true
@config.logger.info { "[LDClient] Initializing polling connection" }
create_worker
@ready
end

def stop
Expand All @@ -39,6 +41,7 @@ def poll
})
if @initialized.make_true
@config.logger.info { "[LDClient] Polling connection initialized" }
@ready.set
end
end
end
Expand All @@ -47,20 +50,24 @@ def create_worker
@worker = Thread.new do
@config.logger.debug { "[LDClient] Starting polling worker" }
while !@stopped.value do
started_at = Time.now
begin
started_at = Time.now
poll
delta = @config.poll_interval - (Time.now - started_at)
if delta > 0
sleep(delta)
rescue UnexpectedResponseError => e
message = Util.http_error_message(e.status, "polling request", "will retry")
@config.logger.error { "[LDClient] #{message}" };
if !Util.http_error_recoverable?(e.status)
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
stop
end
rescue InvalidSDKKeyError
@config.logger.error { "[LDClient] Received 401 error, no further polling requests will be made since SDK key is invalid" };
stop
rescue StandardError => exn
@config.logger.error { "[LDClient] Exception while polling: #{exn.inspect}" }
# TODO: log_exception(__method__.to_s, exn)
end
delta = @config.poll_interval - (Time.now - started_at)
if delta > 0
sleep(delta)
end
end
end
end
Expand Down
24 changes: 10 additions & 14 deletions lib/ldclient-rb/requestor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

module LaunchDarkly

class InvalidSDKKeyError < StandardError
class UnexpectedResponseError < StandardError
def initialize(status)
@status = status
end

def status
@status
end
end

class Requestor
Expand All @@ -13,7 +20,7 @@ def initialize(sdk_key, config)
@config = config
@client = Faraday.new do |builder|
builder.use :http_cache, store: @config.cache_store

builder.adapter :net_http_persistent
end
end
Expand Down Expand Up @@ -44,19 +51,8 @@ def make_request(path)

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

if res.status == 401
@config.logger.error { "[LDClient] Invalid SDK key" }
raise InvalidSDKKeyError
end

if res.status == 404
@config.logger.error { "[LDClient] Resource not found" }
return nil
end

if res.status < 200 || res.status >= 300
@config.logger.error { "[LDClient] Unexpected status code #{res.status}" }
return nil
raise UnexpectedResponseError.new(res.status)
end

JSON.parse(res.body, symbolize_names: true)
Expand Down
Loading

0 comments on commit afea4af

Please sign in to comment.