flipper 0.25.4 → 0.26.0.rc2

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: 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: