Skip to content

Commit f94ce0a

Browse files
authored
Merge pull request #55 from Flagsmith/fix/squash_exceptions_for_polling_manager
fix: Squash exceptions for polling manager
2 parents 68b796b + b680f10 commit f94ce0a

File tree

5 files changed

+77
-11
lines changed

5 files changed

+77
-11
lines changed

lib/flagsmith.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Client # rubocop:disable Metrics/ClassLength
4545
#
4646
# :environment_key, :api_url, :custom_headers, :request_timeout_seconds, :enable_local_evaluation,
4747
# :environment_refresh_interval_seconds, :retries, :enable_analytics, :default_flag_handler,
48-
# :offline_mode, :offline_handler
48+
# :offline_mode, :offline_handler, :polling_manager_failure_limit
4949
#
5050
# You can see full description in the Flagsmith::Config
5151

@@ -107,7 +107,7 @@ def environment_data_polling_manager
107107
update_environment if @environment_data_polling_manager.nil?
108108

109109
@environment_data_polling_manager ||= Flagsmith::EnvironmentDataPollingManager.new(
110-
self, environment_refresh_interval_seconds
110+
self, environment_refresh_interval_seconds, @config.polling_manager_failure_limit
111111
).tap(&:start)
112112
end
113113

lib/flagsmith/sdk/config.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Config
77
OPTIONS = %i[
88
environment_key api_url custom_headers request_timeout_seconds enable_local_evaluation
99
environment_refresh_interval_seconds retries enable_analytics default_flag_handler
10-
offline_mode offline_handler logger
10+
offline_mode offline_handler polling_manager_failure_limit logger
1111
].freeze
1212

1313
# Available Configs
@@ -38,6 +38,8 @@ class Config
3838
# bypasses requests to the api.
3939
# +offline_handler+ - A file object that contains a JSON serialization of
4040
# the entire environment, project, flags, etc.
41+
# +polling_manager_failure_limit+ - An integer to control how long to suppress errors in
42+
# the polling manager for local evaluation mode.
4143
# +logger+ - Pass your logger, default is Logger.new($stdout)
4244
#
4345
attr_reader(*OPTIONS)
@@ -89,6 +91,7 @@ def build_config(options)
8991
@default_flag_handler = opts[:default_flag_handler]
9092
@offline_mode = opts.fetch(:offline_mode, false)
9193
@offline_handler = opts[:offline_handler]
94+
@polling_manager_failure_limit = opts.fetch(:polling_manager_failure_limit, 10)
9295
@logger = options.fetch(:logger, Logger.new($stdout).tap { |l| l.level = :debug })
9396
end
9497
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength

lib/flagsmith/sdk/pooling_manager.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,33 @@ module Flagsmith
77
class EnvironmentDataPollingManager
88
include Flagsmith::SDK::Intervals
99

10-
def initialize(main, refresh_interval_seconds)
10+
attr_reader :failures_since_last_update
11+
12+
def initialize(main, refresh_interval_seconds, polling_manager_failure_limit)
1113
@main = main
1214
@refresh_interval_seconds = refresh_interval_seconds
15+
@polling_manager_failure_limit = polling_manager_failure_limit
16+
@failures_since_last_update = 0
1317
end
1418

19+
# rubocop:disable Metrics/MethodLength
1520
def start
1621
update_environment = lambda {
1722
stop
18-
@interval = set_interval(@refresh_interval_seconds) { @main.update_environment }
23+
@interval = set_interval(@refresh_interval_seconds) do
24+
@main.update_environment
25+
@failures_since_last_update = 0
26+
rescue StandardError => e
27+
@failures_since_last_update += 1
28+
@main.config.logger.warn "Failure to update the environment due to an error: #{e}"
29+
raise e if @failures_since_last_update > @polling_manager_failure_limit
30+
end
1931
}
2032

2133
# TODO: this call should be awaited for getIdentityFlags/getEnvironmentFlags when enableLocalEvaluation is true
2234
update_environment.call
2335
end
36+
# rubocop:enable Metrics/MethodLength
2437

2538
def stop
2639
return unless @interval
Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# frozen_string_literal: true
2-
32
require 'spec_helper'
43

54
require_relative 'shared_mocks.rb'
@@ -8,18 +7,18 @@
87
include_context "shared mocks"
98

109
let(:api_environment_response) { File.read('spec/sdk/fixtures/environment.json') }
11-
let(:environemnt_response) { OpenStruct.new(body: JSON.parse(api_environment_response, symbolize_names: true)) }
10+
let(:environment_response) { OpenStruct.new(body: JSON.parse(api_environment_response, symbolize_names: true)) }
1211
let(:refresh_interval_seconds) { 0.01 }
1312
let(:delay_time) { 0.045 }
1413

1514
before(:each) do
1615
allow(Thread).to receive(:new).and_call_original
1716
allow(Thread).to receive(:kill).and_call_original
18-
allow(mock_api_client).to receive(:get).with('environment-document/').and_return(environemnt_response)
17+
allow(mock_api_client).to receive(:get).with('environment-document/').and_return(environment_response)
1918
allow(double(Flagsmith::Config)).to receive(:environment_url).and_return("environment-document/")
2019
end
2120

22-
subject { Flagsmith::EnvironmentDataPollingManager.new(flagsmith, refresh_interval_seconds) }
21+
subject { Flagsmith::EnvironmentDataPollingManager.new(flagsmith, refresh_interval_seconds, 10) }
2322

2423
it 'test_polling_manager_calls_update_environment_on_start' do
2524
times = (delay_time / refresh_interval_seconds).to_i
@@ -29,3 +28,54 @@
2928
subject.stop
3029
end
3130
end
31+
32+
33+
class FakeFlagsmith
34+
attr_accessor :raise_error, :config
35+
36+
def initialize config
37+
@config = config
38+
end
39+
40+
def update_environment
41+
if @raise_error
42+
raise StandardError, "Some networking issue"
43+
else
44+
# Perform update logic
45+
end
46+
end
47+
end
48+
49+
RSpec.describe Flagsmith::EnvironmentDataPollingManager do
50+
include_context "shared mocks"
51+
52+
let(:refresh_interval_seconds) { 0.01 }
53+
let(:delay_time) { 0.045 }
54+
let(:update_failures_limit) { 5 }
55+
let(:fake_flagsmith) { FakeFlagsmith.new mock_config }
56+
57+
before :each do
58+
allow(mock_config).to receive(:logger).and_return(Logger.new($stdout))
59+
end
60+
61+
subject { Flagsmith::EnvironmentDataPollingManager.new(fake_flagsmith, refresh_interval_seconds, update_failures_limit) }
62+
63+
it "operates under an error prone environment" do
64+
fake_flagsmith.raise_error = true
65+
66+
# Four invocations are processed without the error bubbling up.
67+
times = (delay_time / refresh_interval_seconds).to_i
68+
subject.start
69+
sleep delay_time
70+
# Show that the failures are recorded without raising.
71+
expect(subject.failures_since_last_update).to eq(4)
72+
73+
# Now set flagsmith to respond as normal.
74+
fake_flagsmith.raise_error = false
75+
sleep delay_time
76+
77+
# Now the exception count is back to zero.
78+
expect(subject.failures_since_last_update).to eq(0)
79+
subject.stop
80+
end
81+
end

spec/sdk/flagsmith_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@
111111

112112
describe '#get_identity_segments' do
113113
it 'returns an empty list given an identity which matches no segment conditions' do
114-
polling_manager = Flagsmith::EnvironmentDataPollingManager.new(subject, 60)
114+
polling_manager = Flagsmith::EnvironmentDataPollingManager.new(subject, 60, 10)
115115
subject.update_environment()
116116
expect(subject.get_identity_segments("identifier")).to eq([])
117117
end
118118

119119
it 'returns the relevant segment given an identity with matching traits' do
120-
polling_manager = Flagsmith::EnvironmentDataPollingManager.new(subject, 60)
120+
polling_manager = Flagsmith::EnvironmentDataPollingManager.new(subject, 60, 10)
121121
subject.update_environment()
122122
expect(subject.get_identity_segments("identifier", {"age": 39}).length).to eq(1)
123123
end

0 commit comments

Comments
 (0)