Skip to content

Commit ff3d9d3

Browse files
authored
Merge pull request #795 from flippercloud/poll-cold-start-fix
Poll cold start fix
2 parents c2d1c12 + 67b03ca commit ff3d9d3

File tree

5 files changed

+68
-6
lines changed

5 files changed

+68
-6
lines changed

lib/flipper/adapters/http.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def get_all
5959
response = @client.get("/features?exclude_gate_names=true")
6060
raise Error, response unless response.is_a?(Net::HTTPOK)
6161

62-
parsed_response = Typecast.from_json(response.body)
63-
parsed_features = parsed_response.fetch('features')
62+
parsed_response = response.body.empty? ? {} : Typecast.from_json(response.body)
63+
parsed_features = parsed_response['features'] || []
6464
gates_by_key = parsed_features.each_with_object({}) do |parsed_feature, hash|
6565
hash[parsed_feature['key']] = parsed_feature['gates']
6666
hash

lib/flipper/adapters/poll.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ def initialize(poller, adapter)
1818
@adapter = adapter
1919
@poller = poller
2020
@last_synced_at = 0
21+
22+
# If the adapter is empty, we need to sync before starting the poller.
23+
# Yes, this will block the main thread, but that's better than thinking
24+
# nothing is enabled.
25+
if adapter.features.empty?
26+
begin
27+
@poller.sync
28+
rescue => error
29+
# TODO: Warn here that it's possible that no data has been synced
30+
# and flags are being evaluated without flag data being present
31+
# until a sync completes. We rescue to avoid flipper being down
32+
# causing your processes to crash.
33+
end
34+
end
35+
2136
@poller.start
2237
end
2338

spec/flipper/adapters/poll_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require 'flipper/adapters/poll'
2+
3+
RSpec.describe Flipper::Adapters::Poll do
4+
let(:remote_adapter) {
5+
adapter = Flipper::Adapters::Memory.new(threadsafe: true)
6+
flipper = Flipper.new(adapter)
7+
flipper.enable(:search)
8+
flipper.enable(:analytics)
9+
adapter
10+
}
11+
let(:local_adapter) { Flipper::Adapters::Memory.new(threadsafe: true) }
12+
let(:poller) {
13+
Flipper::Poller.get("for_spec", {
14+
start_automatically: false,
15+
remote_adapter: remote_adapter,
16+
})
17+
}
18+
19+
it "syncs in main thread if local adapter is empty" do
20+
instance = described_class.new(poller, local_adapter)
21+
instance.features # call something to force sync
22+
expect(local_adapter.features).to eq(remote_adapter.features)
23+
end
24+
25+
it "does not sync in main thread if local adapter is not empty" do
26+
# make local not empty by importing remote
27+
flipper = Flipper.new(local_adapter)
28+
flipper.import(remote_adapter)
29+
30+
# make a fake poller to verify calls
31+
poller = double("Poller", last_synced_at: Concurrent::AtomicFixnum.new(0))
32+
expect(poller).to receive(:start).twice
33+
expect(poller).not_to receive(:sync)
34+
35+
# create new instance and call something to force sync
36+
instance = described_class.new(poller, local_adapter)
37+
instance.features # call something to force sync
38+
39+
expect(local_adapter.features).to eq(remote_adapter.features)
40+
end
41+
end

spec/flipper/cloud_spec.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
context 'initialize with token and options' do
4545
it 'sets correct url' do
46+
stub_request(:any, %r{fakeflipper.com}).to_return(status: 200)
4647
instance = described_class.new(token: 'asdf', url: 'https://www.fakeflipper.com/sadpanda')
4748
# pardon the nesting...
4849
memoized = instance.adapter
@@ -78,27 +79,31 @@
7879
end
7980

8081
it 'can set debug_output' do
82+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
8183
expect(Flipper::Adapters::Http::Client).to receive(:new)
82-
.with(hash_including(debug_output: STDOUT)).at_least(:once)
84+
.with(hash_including(debug_output: STDOUT)).at_least(:once).and_return(instance)
8385
described_class.new(token: 'asdf', debug_output: STDOUT)
8486
end
8587

8688
it 'can set read_timeout' do
89+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
8790
expect(Flipper::Adapters::Http::Client).to receive(:new)
88-
.with(hash_including(read_timeout: 1)).at_least(:once)
91+
.with(hash_including(read_timeout: 1)).at_least(:once).and_return(instance)
8992
described_class.new(token: 'asdf', read_timeout: 1)
9093
end
9194

9295
it 'can set open_timeout' do
96+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
9397
expect(Flipper::Adapters::Http::Client).to receive(:new)
94-
.with(hash_including(open_timeout: 1)).at_least(:once)
98+
.with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
9599
described_class.new(token: 'asdf', open_timeout: 1)
96100
end
97101

98102
if RUBY_VERSION >= '2.6.0'
99103
it 'can set write_timeout' do
104+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
100105
expect(Flipper::Adapters::Http::Client).to receive(:new)
101-
.with(hash_including(open_timeout: 1)).at_least(:once)
106+
.with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
102107
described_class.new(token: 'asdf', open_timeout: 1)
103108
end
104109
end

spec/flipper/engine_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
end
1313

1414
before do
15+
stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
1516
Rails.application = nil
1617
ActiveSupport::Dependencies.autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup
1718
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup

0 commit comments

Comments
 (0)