Skip to content

Commit 429512f

Browse files
Merge pull request #17 from statsig-io/1-25-0
v1.25.0
2 parents 7f1271e + 16fc72a commit 429512f

15 files changed

+210
-67
lines changed

.github/workflows/build-and-test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ jobs:
4848

4949
- name: Code Coverage Report
5050
uses: irongut/CodeCoverageSummary@v1.3.0
51+
if: ${{ matrix.version }} == 3.2.0
5152
with:
5253
filename: coverage/coverage.xml
5354
badge: true
@@ -61,7 +62,7 @@ jobs:
6162

6263
- name: Add Coverage PR Comment
6364
uses: marocchino/sticky-pull-request-comment@v2
64-
if: github.event_name == 'pull_request'
65+
if: github.event_name == 'pull_request' && ${{ matrix.version }} == 3.2.0
6566
with:
6667
recreate: true
6768
path: code-coverage-results.md

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ Style/FrozenStringLiteralComment:
1515
Style/Documentation:
1616
Enabled: false
1717
Style/IfUnlessModifier:
18+
Enabled: false
19+
Style/ConditionalAssignment:
1820
Enabled: false

lib/evaluator.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
require 'client_initialize_helpers'
77
require 'spec_store'
88
require 'time'
9-
require 'user_agent_parser'
9+
require 'ua_parser'
1010
require 'evaluation_details'
1111
require 'user_agent_parser/operating_system'
1212

@@ -19,8 +19,8 @@ class Evaluator
1919

2020
def initialize(network, options, error_callback, init_diagnostics = nil)
2121
@spec_store = Statsig::SpecStore.new(network, options, error_callback, init_diagnostics)
22-
@ua_parser = UserAgentParser::Parser.new
23-
CountryLookup.initialize
22+
UAParser.initialize_async
23+
CountryLookup.initialize_async
2424

2525
@gate_overrides = {}
2626
@config_overrides = {}
@@ -449,16 +449,16 @@ def get_value_from_ua(user, field)
449449

450450
case field.downcase
451451
when 'os_name', 'osname'
452-
os = @ua_parser.parse_os(ua)
452+
os = UAParser.parse_os(ua)
453453
return os&.family
454454
when 'os_version', 'osversion'
455-
os = @ua_parser.parse_os(ua)
455+
os = UAParser.parse_os(ua)
456456
return os&.version unless os&.version.nil?
457457
when 'browser_name', 'browsername'
458-
parsed = @ua_parser.parse_ua(ua)
458+
parsed = UAParser.parse_ua(ua)
459459
return parsed.family
460460
when 'browser_version', 'browserversion'
461-
parsed = @ua_parser.parse_ua(ua)
461+
parsed = UAParser.parse_ua(ua)
462462
return parsed.version.to_s
463463
else
464464
nil

lib/network.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def initialize(server_secret, options, backoff_mult = 10)
3333
@local_mode = options.local_mode
3434
@timeout = options.network_timeout
3535
@backoff_multiplier = backoff_mult
36+
@post_logs_retry_backoff = options.post_logs_retry_backoff
3637
@post_logs_retry_limit = options.post_logs_retry_limit
3738
@session_id = SecureRandom.uuid
3839
end
@@ -59,6 +60,13 @@ def post_helper(endpoint, body, retries = 0, backoff = 1)
5960
http = http.timeout(@timeout)
6061
end
6162
backoff_adjusted = backoff > 10 ? backoff += Random.rand(10) : backoff # to deter overlap
63+
if @post_logs_retry_backoff
64+
if @post_logs_retry_backoff.is_a? Integer
65+
backoff_adjusted = @post_logs_retry_backoff
66+
else
67+
backoff_adjusted = @post_logs_retry_backoff.call(retries)
68+
end
69+
end
6270
begin
6371
res = http.post(@api + endpoint, body: body)
6472
rescue StandardError => e

lib/spec_store.rb

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def initialize(network, options, error_callback, init_diagnostics = nil)
3232

3333
@id_list_thread_pool = Concurrent::FixedThreadPool.new(
3434
options.idlist_threadpool_size,
35+
name: 'statsig-idlist',
3536
max_queue: 100,
3637
fallback_policy: :discard,
3738
)
@@ -309,6 +310,16 @@ def process_id_lists(new_id_lists, init_diagnostics, from_adapter: false)
309310

310311
init_diagnostics&.mark("get_id_lists", "start", "process", new_id_lists.length)
311312

313+
delete_lists = []
314+
local_id_lists.each do |list_name, list|
315+
unless new_id_lists.key? list_name
316+
delete_lists.push list_name
317+
end
318+
end
319+
delete_lists.each do |list_name|
320+
local_id_lists.delete list_name
321+
end
322+
312323
new_id_lists.each do |list_name, list|
313324
new_list = IDList.new(list)
314325
local_list = get_id_list(list_name)
@@ -346,21 +357,7 @@ def process_id_lists(new_id_lists, init_diagnostics, from_adapter: false)
346357
end
347358

348359
result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
349-
if result.state != :fulfilled
350-
init_diagnostics&.mark("get_id_lists", "end", "process", false)
351-
return # timed out
352-
end
353-
354-
delete_lists = []
355-
local_id_lists.each do |list_name, list|
356-
unless new_id_lists.key? list_name
357-
delete_lists.push list_name
358-
end
359-
end
360-
delete_lists.each do |list_name|
361-
local_id_lists.delete list_name
362-
end
363-
init_diagnostics&.mark("get_id_lists", "end", "process", true)
360+
init_diagnostics&.mark("get_id_lists", "end", "process", result.state == :fulfilled)
364361
end
365362

366363
def get_single_id_list_from_adapter(list)

lib/statsig_logger.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ def initialize(network, options)
1515
@options = options
1616

1717
@logging_pool = Concurrent::ThreadPoolExecutor.new(
18-
min_threads: [2, Concurrent.processor_count].min,
19-
max_threads: [5, Concurrent.processor_count].min,
18+
name: 'statsig-logger',
19+
min_threads: @options.logger_threadpool_size,
20+
max_threads: @options.logger_threadpool_size,
2021
# max jobs pending before we start dropping
21-
max_queue: [5, Concurrent.processor_count].min * 5,
22+
max_queue: 100,
2223
fallback_policy: :discard
2324
)
2425

@@ -106,7 +107,7 @@ def periodic_flush
106107
Thread.new do
107108
loop do
108109
sleep @options.logging_interval_seconds
109-
flush
110+
flush_async
110111
@interval += 1
111112
@deduper.clear if @interval % 2 == 0
112113
end

lib/statsig_options.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ class StatsigOptions
6464
# default: 3
6565
attr_accessor :idlist_threadpool_size
6666

67+
sig { returns(Integer) }
68+
# The number of threads allocated to posting event logs.
69+
# default: 3
70+
attr_accessor :logger_threadpool_size
71+
6772
sig { returns(T::Boolean) }
6873
# Should diagnostics be logged. These include performance metrics for initialize.
6974
# default: false
@@ -83,6 +88,11 @@ class StatsigOptions
8388
# Number of times to retry sending a batch of failed log events
8489
attr_accessor :post_logs_retry_limit
8590

91+
sig { returns(T.any(Method, Proc, Integer, NilClass)) }
92+
# The number of seconds, or a function that returns the number of seconds based on the number of retries remaining
93+
# which overrides the default backoff time between retries
94+
attr_accessor :post_logs_retry_backoff
95+
8696
sig do
8797
params(
8898
environment: T.any(T::Hash[String, String], NilClass),
@@ -96,10 +106,12 @@ class StatsigOptions
96106
rules_updated_callback: T.any(Method, Proc, NilClass),
97107
data_store: T.any(Statsig::Interfaces::IDataStore, NilClass),
98108
idlist_threadpool_size: Integer,
109+
logger_threadpool_size: Integer,
99110
disable_diagnostics_logging: T::Boolean,
100111
disable_sorbet_logging_handlers: T::Boolean,
101112
network_timeout: T.any(Integer, NilClass),
102-
post_logs_retry_limit: Integer
113+
post_logs_retry_limit: Integer,
114+
post_logs_retry_backoff: T.any(Method, Proc, Integer, NilClass)
103115
).void
104116
end
105117

@@ -115,10 +127,12 @@ def initialize(
115127
rules_updated_callback: nil,
116128
data_store: nil,
117129
idlist_threadpool_size: 3,
130+
logger_threadpool_size: 3,
118131
disable_diagnostics_logging: false,
119132
disable_sorbet_logging_handlers: false,
120133
network_timeout: nil,
121-
post_logs_retry_limit: 3)
134+
post_logs_retry_limit: 3,
135+
post_logs_retry_backoff: nil)
122136
@environment = environment.is_a?(Hash) ? environment : nil
123137
@api_url_base = api_url_base
124138
@rulesets_sync_interval = rulesets_sync_interval
@@ -130,9 +144,11 @@ def initialize(
130144
@rules_updated_callback = rules_updated_callback
131145
@data_store = data_store
132146
@idlist_threadpool_size = idlist_threadpool_size
147+
@logger_threadpool_size = logger_threadpool_size
133148
@disable_diagnostics_logging = disable_diagnostics_logging
134149
@disable_sorbet_logging_handlers = disable_sorbet_logging_handlers
135150
@network_timeout = network_timeout
136151
@post_logs_retry_limit = post_logs_retry_limit
152+
@post_logs_retry_backoff = post_logs_retry_backoff
137153
end
138154
end

lib/ua_parser.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
require 'user_agent_parser'
2+
3+
module UAParser
4+
class Parser
5+
def initialize
6+
@ua_parser = UserAgentParser::Parser.new
7+
end
8+
9+
def parse_os(*args)
10+
@ua_parser.parse_os(*args)
11+
end
12+
13+
def parse_ua(*args)
14+
@ua_parser.parse_ua(*args)
15+
end
16+
17+
def parse_device(*args)
18+
@ua_parser.parse_device(*args)
19+
end
20+
end
21+
22+
def self.initialize
23+
if !@initialize_bg_thread.nil? && @initialize_bg_thread.alive?
24+
@initialize_bg_thread.kill.join
25+
end
26+
@parser = Parser.new
27+
end
28+
29+
def self.initialize_async
30+
if !@initialize_bg_thread.nil? && @initialize_bg_thread.alive?
31+
@initialize_bg_thread.kill.join
32+
end
33+
@initialize_bg_thread = Thread.new { @parser = Parser.new }
34+
@initialize_bg_thread
35+
end
36+
37+
def self.parse_os(*args)
38+
if @parser.nil?
39+
initialize
40+
end
41+
@parser.parse_os(*args)
42+
end
43+
44+
def self.parse_ua(*args)
45+
if @parser.nil?
46+
initialize
47+
end
48+
@parser.parse_ua(*args)
49+
end
50+
51+
def self.parse_device(*args)
52+
if @parser.nil?
53+
initialize
54+
end
55+
@parser.parse_device(*args)
56+
end
57+
end

statsig.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Gem::Specification.new do |s|
44
s.name = 'statsig'
5-
s.version = '1.24.6'
5+
s.version = '1.25.0'
66
s.summary = 'Statsig server SDK for Ruby'
77
s.description = 'Statsig server SDK for feature gates and experimentation in Ruby'
88
s.authors = ['Statsig, Inc']
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
2727
s.add_development_dependency 'simplecov-cobertura', '~> 2.1'
2828
s.add_runtime_dependency 'user_agent_parser', '~> 2.15.0'
2929
s.add_runtime_dependency 'http', '>= 4.4', '< 6.0'
30-
s.add_runtime_dependency 'ip3country', '0.1.1'
30+
s.add_runtime_dependency 'ip3country', '~> 0.2.1'
3131
s.add_runtime_dependency 'sorbet-runtime', '~> 0.5.10461'
3232
s.add_runtime_dependency 'concurrent-ruby', '~> 1.1'
3333
end

test/data_adapter_test.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_datastore_overwritten_by_network
4343
options.data_store = DummyDataAdapter.new
4444
driver = StatsigDriver.new('secret-testcase', options)
4545

46-
sleep 2
46+
sleep 1.1
4747

4848
adapter_specs = options.data_store&.get(Statsig::Interfaces::IDataStore::CONFIG_SPECS_KEY)
4949
specs_json = JSON.parse(adapter_specs)
@@ -98,7 +98,7 @@ def test_datastore_used_for_polling
9898
options.data_store.remove_feature_gate("gate_from_adapter")
9999
options.data_store.update_id_lists
100100

101-
sleep 1
101+
sleep 1.1
102102

103103
result = driver.check_gate(@user, "gate_from_adapter")
104104
assert(result == false)
@@ -122,7 +122,7 @@ def test_datastore_fallback_to_network
122122

123123
options.data_store.corrupt_store
124124

125-
sleep 1
125+
sleep 1.1
126126

127127
result = driver.check_gate(@user, "gate_from_adapter")
128128
assert(result == false)

0 commit comments

Comments
 (0)