flipper 0.25.4 → 0.26.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a857af3f65cd468bfeeadab8c94c8ca5bf4c27cad345d93f05e5993a8f53a57d
4
- data.tar.gz: dcbe82968b60be3a46b66e0f19ddf3416c8830bd63c3926fd6ca77b45e404e02
3
+ metadata.gz: 60ce7896afa06dd25ad8e6f8b4f419bbb992e16ce7147339af30ac15f338871d
4
+ data.tar.gz: 3a6635db6af529d29a2f1bd075fe45a82c77211bdd012d3613a89f2b98566975
5
5
  SHA512:
6
- metadata.gz: '09914ec0d548868bf62ba0c3fe07cdaf4f773897d1416ec499c0a2f2355e6d38db96689c24d80a26b32ad2ecfbd6f380dd5da3e7cd44248c461826287252a487'
7
- data.tar.gz: 4518fa261e1f265bdf1718e52606bf0b25b1e84fa0431bbe64359797f073dc3cbb1d075b9961bfc054bab19e32e1edbcd2bdce37eb2d988853684bbbcf43c621
6
+ metadata.gz: 6ce977a03bcafdfd4cb0427541eb0277b4dfbbadd723c86e12a7fcbec65d398663ed74fbdb9c46aa6b460e00e0025bd9853ed52c7a3cf3f44016e005d79e6eff
7
+ data.tar.gz: c422114b0e7076ebd7c0c63bef943a54aede502f253c5310a4e59f17daaadeafd435594aac7936c441adfc3a39fa3d5ba2ef17c589d828cefb5a977d750f0215
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,125 @@
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
+ attr_reader :thread, :pid, :mutex, :interval, :last_synced_at
11
+
12
+ def self.instances
13
+ @instances ||= Concurrent::Map.new
14
+ end
15
+ private_class_method :instances
16
+
17
+ def self.get(key, options = {})
18
+ instances.compute_if_absent(key) { new(options) }
19
+ end
20
+
21
+ def self.reset
22
+ instances.clear
23
+ end
24
+
25
+ def initialize(options = {})
26
+ @thread = nil
27
+ @pid = Process.pid
28
+ @mutex = Mutex.new
29
+ @adapter = Memory.new
30
+ @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
31
+ @remote_adapter = options.fetch(:remote_adapter)
32
+ @interval = options.fetch(:interval, 10).to_f
33
+ @lock = Concurrent::ReadWriteLock.new
34
+ @last_synced_at = Concurrent::AtomicFixnum.new(0)
35
+
36
+ if @interval < 1
37
+ warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
38
+ @interval = 1
39
+ end
40
+
41
+ @start_automatically = options.fetch(:start_automatically, true)
42
+
43
+ if options.fetch(:shutdown_automatically, true)
44
+ at_exit { stop }
45
+ end
46
+ end
47
+
48
+ def adapter
49
+ @lock.with_read_lock { Memory.new(@adapter.get_all.dup) }
50
+ end
51
+
52
+ def start
53
+ reset if forked?
54
+ ensure_worker_running
55
+ end
56
+
57
+ def stop
58
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", {
59
+ operation: :stop,
60
+ })
61
+ @thread&.kill
62
+ end
63
+
64
+ def run
65
+ loop do
66
+ sleep jitter
67
+ start = Concurrent.monotonic_time
68
+ begin
69
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", operation: :poll) do
70
+ adapter = Memory.new
71
+ adapter.import(@remote_adapter)
72
+
73
+ @lock.with_write_lock { @adapter.import(adapter) }
74
+ @last_synced_at.update { |time| Concurrent.monotonic_time }
75
+ end
76
+ rescue => exception
77
+ # you can instrument these using poller.flipper
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
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", {
107
+ operation: :thread_start,
108
+ })
109
+ ensure
110
+ mutex.unlock
111
+ end
112
+ end
113
+
114
+ def thread_alive?
115
+ @thread && @thread.alive?
116
+ end
117
+
118
+ def reset
119
+ @pid = Process.pid
120
+ mutex.unlock if mutex.locked?
121
+ end
122
+ end
123
+ end
124
+ end
125
+ 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.rc2'.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.rc2
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-16 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: