Skip to content

Commit

Permalink
Merge 23e5740 into 4779a6c
Browse files Browse the repository at this point in the history
  • Loading branch information
rarruda committed Jan 18, 2019
2 parents 4779a6c + 23e5740 commit 9ed0021
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 36 deletions.
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -168,6 +168,16 @@ if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
end
```

#### Client methods

Method Name | Description | Return Type |
---------|-------------|-------------|
`is_enabled?` | Check if feature toggle is to be enabled or not. | Boolean |
`enabled?` | Alias to the `is_enabled?` method. But more ruby idiomatic. | Boolean |
`shutdown` | Save metrics to disk, flush metrics to server, and then kill ToggleFetcher and MetricsReporter threads. A safe shutdown. Not really useful in long running applications, like web applications. | nil |
`shutdown!` | Kill ToggleFetcher and MetricsReporter threads immediately. | nil |


## Local test client

```
Expand Down
8 changes: 8 additions & 0 deletions examples/simple.rb
Expand Up @@ -50,4 +50,12 @@
puts "> #{feature_name} is not enabled"
end

puts "> shutting down client..."

@unleash.shutdown

sleep 1

puts ">> END simple.rb"


34 changes: 32 additions & 2 deletions lib/unleash/client.rb
@@ -1,13 +1,16 @@
require 'unleash/configuration'
require 'unleash/toggle_fetcher'
require 'unleash/metrics_reporter'
require 'unleash/scheduled_executor'
require 'unleash/feature_toggle'
require 'logger'
require 'time'

module Unleash

class Client
attr_accessor :fetcher_scheduled_executor, :metrics_scheduled_executor

def initialize(*opts)
Unleash.configuration ||= Unleash::Configuration.new(*opts)
Unleash.configuration.validate!
Expand All @@ -17,13 +20,19 @@ def initialize(*opts)

unless Unleash.configuration.disable_client
Unleash.toggle_fetcher = Unleash::ToggleFetcher.new

register

self.fetcher_scheduled_executor = Unleash::ScheduledExecutor.new('ToggleFetcher', Unleash.configuration.refresh_interval)
self.fetcher_scheduled_executor.run do
Unleash.toggle_fetcher.fetch
end

unless Unleash.configuration.disable_metrics
Unleash.toggle_metrics = Unleash::Metrics.new
Unleash.reporter = Unleash::MetricsReporter.new
scheduledExecutor = Unleash::ScheduledExecutor.new('MetricsReporter', Unleash.configuration.metrics_interval)
scheduledExecutor.run do
self.metrics_scheduled_executor = Unleash::ScheduledExecutor.new('MetricsReporter', Unleash.configuration.metrics_interval)
self.metrics_scheduled_executor.run do
Unleash.reporter.send
end
end
Expand Down Expand Up @@ -53,6 +62,27 @@ def is_enabled?(feature, context = nil, default_value = false)
return toggle_result
end

# enabled? is a more ruby idiomatic method name than is_enabled?
alias_method :enabled?, :is_enabled?


# safe shutdown: also flush metrics to server and toggles to disk
def shutdown
unless Unleash.configuration.disable_client
Unleash.toggle_fetcher.save!
Unleash.reporter.send unless Unleash.configuration.disable_metrics
shutdown!
end
end

# quick shutdown: just kill running threads
def shutdown!
unless Unleash.configuration.disable_client
self.fetcher_scheduled_executor.exit
self.metrics_scheduled_executor.exit unless Unleash.configuration.disable_metrics
end
end

private
def info
return {
Expand Down
5 changes: 1 addition & 4 deletions lib/unleash/metrics_reporter.rb
Expand Up @@ -7,15 +7,12 @@
module Unleash

class MetricsReporter
attr_accessor :last_time, :client
attr_accessor :last_time

def initialize
self.last_time = Time.now
end

def build_hash
end

def generate_report
now = Time.now
start, stop, self.last_time = self.last_time, now, now
Expand Down
20 changes: 16 additions & 4 deletions lib/unleash/scheduled_executor.rb
@@ -1,17 +1,18 @@
module Unleash

class ScheduledExecutor
attr_accessor :name, :interval, :max_exceptions, :retry_count
attr_accessor :name, :interval, :max_exceptions, :retry_count, :thread

def initialize(name, interval, max_exceptions = 5)
self.name = name || ''
self.interval = interval
self.max_exceptions = max_exceptions
self.retry_count = 0
self.thread = nil
end

def run(&blk)
thread = Thread.new do
self.thread = Thread.new do
Thread.current[:name] = self.name

loop do
Expand All @@ -29,11 +30,22 @@ def run(&blk)
end

if self.retry_count > self.max_exceptions
Unleash.logger.info "thread #{name} retry_count (#{self.retry_count}) exceeded max_exceptions (#{self.max_exceptions}). Stopping with retries."
Unleash.logger.error "thread #{name} retry_count (#{self.retry_count}) exceeded max_exceptions (#{self.max_exceptions}). Stopping with retries."
break
end
end
Unleash.logger.info "thread #{name} ended"
Unleash.logger.warn "thread #{name} loop ended"
end
end

def running?
self.thread.is_a?(Thread) && self.thread.alive?
end

def exit
if self.running?
Unleash.logger.warn "thread #{name} will exit!"
self.thread.exit
end
end
end
Expand Down
50 changes: 24 additions & 26 deletions lib/unleash/toggle_fetcher.rb
@@ -1,5 +1,4 @@
require 'unleash/configuration'
require 'unleash/scheduled_executor'
require 'net/http'
require 'json'
require 'thread'
Expand All @@ -24,9 +23,7 @@ def initialize
read!
end

# once we have initialized, start the fetcher loop
scheduledExecutor = Unleash::ScheduledExecutor.new('ToggleFetcher', Unleash.configuration.refresh_interval)
scheduledExecutor.run { remote_toggles = fetch() }
# once initialized, somewhere else you will want to start a loop with fetch()
end

def toggles
Expand All @@ -38,7 +35,7 @@ def toggles
end

# rename to refresh_from_server! ??
# TODO: should simplify by moving uri / http initialization to class initialization
# TODO: should simplify by moving uri / http initialization elsewhere
def fetch
Unleash.logger.debug "fetch()"
Unleash.logger.debug "ETag: #{self.etag}" unless self.etag.nil?
Expand Down Expand Up @@ -82,6 +79,27 @@ def fetch
save!
end

def save!
begin
backup_file = Unleash.configuration.backup_file
backup_file_tmp = "#{backup_file}.tmp"

self.toggle_lock.synchronize do
file = File.open(backup_file_tmp, "w")
file.write(self.toggle_cache.to_json)
File.rename(backup_file_tmp, backup_file)
end

rescue Exception => e
# This is not really the end of the world. Swallowing the exception.
Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
Unleash.logger.error "stacktrace: #{e.backtrace}"
ensure
file.close unless file.nil?
self.toggle_lock.unlock if self.toggle_lock.locked?
end
end

private

def synchronize_with_local_cache!(features)
Expand All @@ -102,29 +120,9 @@ def update_client!
end
end

def backup_file_exists?
File.exists?(backup_file)
end

def save!
begin
file = File.open(Unleash.configuration.backup_file, "w")

self.toggle_lock.synchronize do
file.write(self.toggle_cache.to_json)
end
rescue Exception => e
# This is not really the end of the world. Swallowing the exception.
Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
Unleash.logger.error "stacktrace: #{e.backtrace}"
ensure
file.close unless file.nil?
end
end

def read!
Unleash.logger.debug "read!()"
return nil unless File.exists?(Unleash.configuration.backup_file)
return nil unless File.exist?(Unleash.configuration.backup_file)

begin
file = File.open(Unleash.configuration.backup_file, "r")
Expand Down
18 changes: 18 additions & 0 deletions spec/unleash/scheduled_executor_spec.rb
@@ -0,0 +1,18 @@
require 'spec_helper'

RSpec.describe Unleash::ScheduledExecutor do
# context 'parameters correctly assigned in initialization'
it "can start and exit a thread" do
scheduled_executor = Unleash::ScheduledExecutor.new('TesterLoop', 0.1)
scheduled_executor.run do
loop do
sleep 0.1
end
end

expect(scheduled_executor.running?).to be true
scheduled_executor.exit
scheduled_executor.thread.join
expect(scheduled_executor.running?).to be false
end
end

0 comments on commit 9ed0021

Please sign in to comment.