Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prepare 5.0.0 release #102

Merged
merged 57 commits into from
Jun 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d0fe10e
refactor to use an Event in update processor init
eli-darkly Jun 14, 2018
3755d2b
allow update processors to signal immediate failure
eli-darkly Jun 14, 2018
14424e2
Merge pull request #65 from launchdarkly/eb/ch18860/fail-fast-init
eli-darkly Jun 14, 2018
adf1e0c
fail permanently on most 4xx errors
eli-darkly Jun 15, 2018
d711aea
Merge pull request #66 from launchdarkly/eb/4xx-errors
eli-darkly Jun 15, 2018
c281bac
reimplement SSE client without Celluloid
eli-darkly Jun 20, 2018
58e78bb
rm nio4r dependency
eli-darkly Jun 20, 2018
bffde7f
refactor for better encapsulation of HTTP logic
eli-darkly Jun 20, 2018
38ac599
rm stacktraces
eli-darkly Jun 20, 2018
f87db97
better logging
eli-darkly Jun 20, 2018
a2cde5e
ensure connection is closed
eli-darkly Jun 20, 2018
42d5610
comments
eli-darkly Jun 20, 2018
4c9cc04
don't build on unsupported Ruby versions
eli-darkly Jun 20, 2018
a06ebc1
don't build for JRuby 9.0 either
eli-darkly Jun 20, 2018
a2aafd9
read status code & headers in constructor; add comments
eli-darkly Jun 20, 2018
89251f4
break out streaming support classes into separate files
eli-darkly Jun 20, 2018
61f83ce
revise EventParser to return parsed items via a generator
eli-darkly Jun 20, 2018
9170f8a
throw exception if we lose the connection before we have response hea…
eli-darkly Jun 20, 2018
fec18df
comments & better method name
eli-darkly Jun 20, 2018
291b08e
move SSE classes into separate module
eli-darkly Jun 21, 2018
ee4fa7c
unit tests for event parser
eli-darkly Jun 21, 2018
36a4ca6
move test file
eli-darkly Jun 21, 2018
ef53627
break out method for testability
eli-darkly Jun 21, 2018
d0b257b
break out HTTP response reader into its own class
eli-darkly Jun 21, 2018
51bd8a7
fix method name
eli-darkly Jun 21, 2018
f071b9f
add tests for HTTPResponseReader
eli-darkly Jun 21, 2018
502e99b
Merge pull request #67 from launchdarkly/eb/ch19217/remove-celluloid
eli-darkly Jun 22, 2018
95a9d8e
end-to-end tests for new SSE client [2 of 2]
eli-darkly Jun 22, 2018
bfdfdc8
don't use Thread.raise
eli-darkly Jun 22, 2018
d19c589
rm unnecessary class
eli-darkly Jun 22, 2018
a0b314c
add unit test for chunked encoding
eli-darkly Jun 22, 2018
3b4ca92
misc cleanup
eli-darkly Jun 22, 2018
86cc3bc
misc cleanup
eli-darkly Jun 22, 2018
253c0cd
rm unused var
eli-darkly Jun 22, 2018
e983700
turn logging back on to diagnose test problems
eli-darkly Jun 22, 2018
dcb7af8
make sure we don't try to read past end of response body
eli-darkly Jun 22, 2018
c66a8e6
Merge pull request #69 from launchdarkly/eb/ch19217/http-tests
eli-darkly Jun 22, 2018
9243993
add Ruby 2.5 and JRuby 9.1 to build
eli-darkly Jun 22, 2018
fc86c6d
update readme
eli-darkly Jun 22, 2018
2ac49fa
Merge pull request #70 from launchdarkly/eb/ch19355/ruby-versions
eli-darkly Jun 22, 2018
7357beb
remove mention of Celluloid from readme
eli-darkly Jun 22, 2018
e810adb
Merge pull request #71 from launchdarkly/eb/readme-edit
eli-darkly Jun 22, 2018
c66703d
nil guard to avoid spurious error when shutting down
eli-darkly Jun 22, 2018
718d1a4
fix proxy implementation so it actually works
eli-darkly Jun 22, 2018
776bc98
add unit tests for http/https proxy
eli-darkly Jun 22, 2018
1113c04
add basic HTTPS request test
eli-darkly Jun 23, 2018
f6b8c75
rm obsolete dependencies
eli-darkly Jun 23, 2018
e4ac0ef
always send Host header
eli-darkly Jun 23, 2018
3796a2d
don't try to create an embedded HTTPS server, it doesn't work in JRub…
eli-darkly Jun 23, 2018
e8bc6b7
don't send Host header twice
eli-darkly Jun 23, 2018
58a92ca
don't need to read body
eli-darkly Jun 23, 2018
74b8738
more correct logic for getting proxy from env vars
eli-darkly Jun 23, 2018
c34de47
update readme to mention Socketry
eli-darkly Jun 23, 2018
16c45e5
ditch mutative method, misc cleanup
eli-darkly Jun 25, 2018
228f3aa
factor out socket creation/proxy logic
eli-darkly Jun 25, 2018
06c3214
fix raising of ProxyError, add unit test
eli-darkly Jun 25, 2018
e4eecc1
Merge pull request #72 from launchdarkly/eb/ch19217/proxy
eli-darkly Jun 25, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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