flipper 0.25.4 → 0.26.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a857af3f65cd468bfeeadab8c94c8ca5bf4c27cad345d93f05e5993a8f53a57d
4
- data.tar.gz: dcbe82968b60be3a46b66e0f19ddf3416c8830bd63c3926fd6ca77b45e404e02
3
+ metadata.gz: 5b6c4a5c4ef29f126e9a08cc7fc7c70be9831b179dfc55486b88b8c5da5fdf44
4
+ data.tar.gz: aab15578901fcab2b3717813aa37d3cc057554eea3d442f264f85707d94310f9
5
5
  SHA512:
6
- metadata.gz: '09914ec0d548868bf62ba0c3fe07cdaf4f773897d1416ec499c0a2f2355e6d38db96689c24d80a26b32ad2ecfbd6f380dd5da3e7cd44248c461826287252a487'
7
- data.tar.gz: 4518fa261e1f265bdf1718e52606bf0b25b1e84fa0431bbe64359797f073dc3cbb1d075b9961bfc054bab19e32e1edbcd2bdce37eb2d988853684bbbcf43c621
6
+ metadata.gz: 79989c8d6149b13b6c960878d4ac701f98f5e3da9b44a567092a4ace82350ec8ccffcd7e9c9ea2619a2a2508b6a392e99c05b14ab14b59b5a0b3a58b4f211150
7
+ data.tar.gz: 5192a0b9e1bebc39a28503861ba4eb35a05010a1841595e1282437c7d0694e3eccd02f215fbc6928b228948c6a02c8e9d26f85a5e23ae271fd0b32bc84bf6b53
data/Changelog.md CHANGED
@@ -1,3 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 0.26.0.rc1
6
+
7
+ * Cloud Background Polling (https://github.com/jnunemaker/flipper/pull/682)
8
+
1
9
  ## 0.25.4
2
10
 
3
11
  * Added read_only UI config option (https://github.com/jnunemaker/flipper/pull/679)
data/flipper.gemspec CHANGED
@@ -35,4 +35,6 @@ Gem::Specification.new do |gem|
35
35
  gem.require_paths = ['lib']
36
36
  gem.version = Flipper::VERSION
37
37
  gem.metadata = Flipper::METADATA
38
+
39
+ gem.add_dependency 'concurrent-ruby', '< 2'
38
40
  end
@@ -4,7 +4,7 @@ module Flipper
4
4
  include ::Flipper::Adapter
5
5
 
6
6
  # Public: The name of the adapter.
7
- attr_reader :name
7
+ attr_reader :name, :local, :remote
8
8
 
9
9
  # Public: Build a new sync instance.
10
10
  #
@@ -14,6 +14,10 @@ module Flipper
14
14
 
15
15
  HTTPS_SCHEME = "https".freeze
16
16
 
17
+ attr_reader :uri, :headers
18
+ attr_reader :basic_auth_username, :basic_auth_password
19
+ attr_reader :read_timeout, :open_timeout, :write_timeout, :max_retries, :debug_output
20
+
17
21
  def initialize(options = {})
18
22
  @uri = URI(options.fetch(:url))
19
23
  @headers = DEFAULT_HEADERS.merge(options[:headers] || {})
@@ -22,6 +26,7 @@ module Flipper
22
26
  @read_timeout = options[:read_timeout]
23
27
  @open_timeout = options[:open_timeout]
24
28
  @write_timeout = options[:write_timeout]
29
+ @max_retries = options.key?(:max_retries) ? options[:max_retries] : 0
25
30
  @debug_output = options[:debug_output]
26
31
  end
27
32
 
@@ -58,7 +63,8 @@ module Flipper
58
63
  http = Net::HTTP.new(uri.host, uri.port)
59
64
  http.read_timeout = @read_timeout if @read_timeout
60
65
  http.open_timeout = @open_timeout if @open_timeout
61
- apply_write_timeout(http)
66
+ http.max_retries = @max_retries if @max_retries
67
+ http.write_timeout = @write_timeout if @write_timeout
62
68
  http.set_debug_output(@debug_output) if @debug_output
63
69
 
64
70
  if uri.scheme == HTTPS_SCHEME
@@ -91,16 +97,6 @@ module Flipper
91
97
 
92
98
  request
93
99
  end
94
-
95
- def apply_write_timeout(http)
96
- if @write_timeout
97
- if RUBY_VERSION >= '2.6.0'
98
- http.write_timeout = @write_timeout
99
- else
100
- Kernel.warn("Warning: option :write_timeout requires Ruby version 2.6.0 or later")
101
- end
102
- end
103
- end
104
100
  end
105
101
  end
106
102
  end
@@ -10,7 +10,7 @@ module Flipper
10
10
  class Http
11
11
  include Flipper::Adapter
12
12
 
13
- attr_reader :name
13
+ attr_reader :name, :client
14
14
 
15
15
  def initialize(options = {})
16
16
  @client = Client.new(url: options.fetch(:url),
@@ -19,6 +19,8 @@ module Flipper
19
19
  basic_auth_password: options[:basic_auth_password],
20
20
  read_timeout: options[:read_timeout],
21
21
  open_timeout: options[:open_timeout],
22
+ write_timeout: options[:write_timeout],
23
+ max_retries: options[:max_retries],
22
24
  debug_output: options[:debug_output])
23
25
  @name = :http
24
26
  end
@@ -0,0 +1,123 @@
1
+ require 'logger'
2
+ require 'concurrent/atomic/read_write_lock'
3
+ require 'concurrent/utility/monotonic_time'
4
+ require 'concurrent/map'
5
+
6
+ module Flipper
7
+ module Adapters
8
+ class Poll
9
+ class Poller
10
+ PREFIX = "[flipper http async poll adapter]".freeze
11
+
12
+ attr_reader :thread, :pid, :mutex, :logger, :interval, :last_synced_at
13
+
14
+ def self.instances
15
+ @instances ||= Concurrent::Map.new
16
+ end
17
+ private_class_method :instances
18
+
19
+ def self.get(key, options = {})
20
+ instances.compute_if_absent(key) { new(options) }
21
+ end
22
+
23
+ def self.reset
24
+ instances.clear
25
+ end
26
+
27
+ def initialize(options = {})
28
+ @thread = nil
29
+ @pid = Process.pid
30
+ @mutex = Mutex.new
31
+ @adapter = Memory.new
32
+ @remote_adapter = options.fetch(:remote_adapter)
33
+ @logger = options.fetch(:logger) { Logger.new(STDOUT) }
34
+ @interval = options.fetch(:interval, 10).to_f
35
+ @lock = Concurrent::ReadWriteLock.new
36
+ @last_synced_at = Concurrent::AtomicFixnum.new(0)
37
+
38
+ if @interval < 1
39
+ warn "#{PREFIX} interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
40
+ @interval = 1
41
+ end
42
+
43
+ @start_automatically = options.fetch(:start_automatically, true)
44
+
45
+ if options.fetch(:shutdown_automatically, true)
46
+ at_exit { stop }
47
+ end
48
+ end
49
+
50
+ def adapter
51
+ @lock.with_read_lock { Memory.new(@adapter.get_all.dup) }
52
+ end
53
+
54
+ def start
55
+ reset if forked?
56
+ ensure_worker_running
57
+ end
58
+
59
+ def stop
60
+ logger.debug { "#{PREFIX} Stopping worker" }
61
+ @thread&.kill
62
+ end
63
+
64
+ def run
65
+ loop do
66
+ sleep jitter
67
+ start = Concurrent.monotonic_time
68
+ begin
69
+ logger.debug { "#{PREFIX} Making a checkity checkity" }
70
+
71
+ adapter = Memory.new
72
+ adapter.import(@remote_adapter)
73
+
74
+ @lock.with_write_lock { @adapter.import(adapter) }
75
+ @last_synced_at.update { |time| Concurrent.monotonic_time }
76
+ rescue => exception
77
+ logger.debug { "#{PREFIX} Exception: #{exception.inspect}" }
78
+ end
79
+
80
+ sleep_interval = interval - (Concurrent.monotonic_time - start)
81
+ sleep sleep_interval if sleep_interval.positive?
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def jitter
88
+ rand
89
+ end
90
+
91
+ def forked?
92
+ pid != Process.pid
93
+ end
94
+
95
+ def ensure_worker_running
96
+ # Return early if thread is alive and avoid the mutex lock and unlock.
97
+ return if thread_alive?
98
+
99
+ # If another thread is starting worker thread, then return early so this
100
+ # thread can enqueue and move on with life.
101
+ return unless mutex.try_lock
102
+
103
+ begin
104
+ return if thread_alive?
105
+ @thread = Thread.new { run }
106
+ logger.debug { "#{PREFIX} Worker thread [#{@thread.object_id}] started" }
107
+ ensure
108
+ mutex.unlock
109
+ end
110
+ end
111
+
112
+ def thread_alive?
113
+ @thread && @thread.alive?
114
+ end
115
+
116
+ def reset
117
+ @pid = Process.pid
118
+ mutex.unlock if mutex.locked?
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,35 @@
1
+ require 'flipper/adapters/sync/synchronizer'
2
+
3
+ module Flipper
4
+ module Adapters
5
+ class Poll
6
+ extend Forwardable
7
+ include ::Flipper::Adapter
8
+
9
+ # Public: The name of the adapter.
10
+ attr_reader :name, :adapter, :poller
11
+
12
+ def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
13
+
14
+ def initialize(poller, adapter)
15
+ @name = :poll
16
+ @adapter = adapter
17
+ @poller = poller
18
+ @last_synced_at = 0
19
+ @poller.start
20
+ end
21
+
22
+ private
23
+
24
+ def synced_adapter
25
+ @poller.start
26
+ poller_last_synced_at = @poller.last_synced_at.value
27
+ if poller_last_synced_at > @last_synced_at
28
+ Flipper::Adapters::Sync::Synchronizer.new(@adapter, @poller.adapter).call
29
+ @last_synced_at = poller_last_synced_at
30
+ end
31
+ @adapter
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.25.4'.freeze
2
+ VERSION = '0.26.0.rc1'.freeze
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -22,7 +22,7 @@ Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
22
22
 
23
23
  RSpec.configure do |config|
24
24
  config.before(:example) do
25
- Flipper::Cloud::Registry.default.clear if defined?(Flipper::Cloud)
25
+ Flipper::Adapters::Poll::Poller.reset if defined?(Flipper::Adapters::Poll::Poller)
26
26
  Flipper.unregister_groups
27
27
  Flipper.configuration = nil
28
28
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.4
4
+ version: 0.26.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-07 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
13
27
  description:
14
28
  email:
15
29
  - nunemaker@gmail.com
@@ -68,6 +82,8 @@ files:
68
82
  - lib/flipper/adapters/memoizable.rb
69
83
  - lib/flipper/adapters/memory.rb
70
84
  - lib/flipper/adapters/operation_logger.rb
85
+ - lib/flipper/adapters/poll.rb
86
+ - lib/flipper/adapters/poll/poller.rb
71
87
  - lib/flipper/adapters/pstore.rb
72
88
  - lib/flipper/adapters/read_only.rb
73
89
  - lib/flipper/adapters/sync.rb
@@ -179,9 +195,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
195
  version: '0'
180
196
  required_rubygems_version: !ruby/object:Gem::Requirement
181
197
  requirements:
182
- - - ">="
198
+ - - ">"
183
199
  - !ruby/object:Gem::Version
184
- version: '0'
200
+ version: 1.3.1
185
201
  requirements: []
186
202
  rubygems_version: 3.3.7
187
203
  signing_key: