statsig 2.8.4 → 2.9.1

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: 659ac849e7a883eb05f0a5bcfa2be82e2196c903f97cca9c39a74c5f6753458b
4
- data.tar.gz: 1f2067256ddcc39b3fe078a6776e740d9eb3d3a82326c1a281a83832559fa9a6
3
+ metadata.gz: 800d73e16d593bd9c6b505f9d6207c47e51d8b700344b3c653a07ca8b257cd69
4
+ data.tar.gz: a3cad13252aff42d5eba3e4f9c9b478adb9ec0758633102f2ada9b3d3739052e
5
5
  SHA512:
6
- metadata.gz: 980fb1e0a8dbe70abc86a94f9cb09dccbf215809ae7079c9898d71cb854db86abb308c2b846c3617f8f6c28dadeaca999386459e6faecb82832de062ab47ac2b
7
- data.tar.gz: 63fb183126f3df4648c7167a56cf53aee361ee1c2c68305c0855ee06cb982b4ae9c7e74c1f2ca786136a0ac04cf614519b38e616b43bfa324e6f0028a965704f
6
+ metadata.gz: f9a1df9789f61b60bb4d015da636b20bf081804b5c1c8d1f542afc3f494538fea68b3bc84ca65d7faf513c63d8d0d72bee94f2583a932c7d54ae0c3cdff560e7
7
+ data.tar.gz: a7d46bfa64e8a52c60eec3bc14c596ff9ffe41493ed68bda741d4171c75da407e7e7842ad63c76addff0c98314d95967b515801efba8f1f5486d8aabc793fc8e
data/lib/network.rb CHANGED
@@ -28,26 +28,8 @@ module Statsig
28
28
  @post_logs_retry_backoff = options.post_logs_retry_backoff
29
29
  @post_logs_retry_limit = options.post_logs_retry_limit
30
30
  @session_id = SecureRandom.uuid
31
- @connection_pool = ConnectionPool.new(size: 3) do
32
- meta = Statsig.get_statsig_metadata
33
- client = HTTP.use(:auto_inflate).headers(
34
- {
35
- 'STATSIG-API-KEY' => @server_secret,
36
- 'STATSIG-SERVER-SESSION-ID' => @session_id,
37
- 'Content-Type' => 'application/json; charset=UTF-8',
38
- 'STATSIG-SDK-TYPE' => meta['sdkType'],
39
- 'STATSIG-SDK-VERSION' => meta['sdkVersion'],
40
- 'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
41
- 'Accept-Encoding' => 'gzip'
42
- }
43
- ).accept(:json)
44
-
45
- if @timeout
46
- client = client.timeout(@timeout)
47
- end
48
-
49
- client
50
- end
31
+ @connection_pools = {}
32
+ @connection_pools_mutex = Mutex.new
51
33
  end
52
34
 
53
35
  def download_config_specs(since_time, context)
@@ -69,10 +51,7 @@ module Statsig
69
51
  gzip = Zlib::GzipWriter.new(StringIO.new)
70
52
  gzip << json_body
71
53
 
72
- response, e = post(url, gzip.close.string, @post_logs_retry_limit, 1, true, event_count)
73
-
74
- # Consume response body to ensure connection can be closed.
75
- response&.flush
54
+ _response, e = post(url, gzip.close.string, @post_logs_retry_limit, 1, true, event_count)
76
55
 
77
56
  unless e == nil
78
57
  message = "Failed to log #{event_count} events after #{@post_logs_retry_limit} retries"
@@ -92,6 +71,10 @@ module Statsig
92
71
  post(url, JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }))
93
72
  end
94
73
 
74
+ def download_id_list(url, start_byte = 0)
75
+ request(:GET, url, nil, 0, 1, false, 0, { 'Range' => "bytes=#{start_byte}-" }, false)
76
+ end
77
+
95
78
  def get(url, retries = 0, backoff = 1)
96
79
  request(:GET, url, nil, retries, backoff)
97
80
  end
@@ -100,7 +83,21 @@ module Statsig
100
83
  request(:POST, url, body, retries, backoff, zipped, event_count)
101
84
  end
102
85
 
103
- def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0)
86
+ def shutdown
87
+ pools = @connection_pools_mutex.synchronize do
88
+ pools = @connection_pools.values
89
+ @connection_pools = {}
90
+ pools
91
+ end
92
+
93
+ pools.each do |pool|
94
+ pool.shutdown do |conn|
95
+ conn.close if conn.respond_to?(:close)
96
+ end
97
+ end
98
+ end
99
+
100
+ def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true)
104
101
  if @local_mode
105
102
  return nil, nil
106
103
  end
@@ -114,18 +111,25 @@ module Statsig
114
111
  end
115
112
 
116
113
  begin
117
- res = @connection_pool.with do |conn|
118
- request = conn.headers(
119
- 'STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s,
120
- 'CONTENT-ENCODING' => zipped ? 'gzip' : nil,
121
- 'STATSIG-EVENT-COUNT' => event_count == 0 ? nil : event_count.to_s
122
- )
123
-
124
- case method
125
- when :GET
126
- request.get(url)
127
- when :POST
128
- request.post(url, body: body)
114
+ pool = connection_pool_for(url)
115
+ res = pool.with do |conn|
116
+ begin
117
+ request_headers = build_request_headers(zipped, event_count, extra_headers, use_statsig_headers)
118
+ request = conn.headers(request_headers)
119
+
120
+ response = case method
121
+ when :GET
122
+ request.get(url)
123
+ when :POST
124
+ request.post(url, body: body)
125
+ end
126
+
127
+ # Fully drain the response before the client returns to the pool.
128
+ response.body.to_s
129
+ response
130
+ rescue StandardError
131
+ pool.discard_current_connection(&:close)
132
+ raise
129
133
  end
130
134
  end
131
135
  rescue StandardError => e
@@ -133,7 +137,7 @@ module Statsig
133
137
  return nil, e unless retries.positive?
134
138
 
135
139
  sleep backoff_adjusted
136
- return request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
140
+ return request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
137
141
  end
138
142
  return res, nil if res.status.success?
139
143
 
@@ -144,7 +148,50 @@ module Statsig
144
148
 
145
149
  ## status code retry
146
150
  sleep backoff_adjusted
147
- request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count)
151
+ request(method, url, body, retries - 1, backoff * @backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers)
152
+ end
153
+
154
+ private
155
+
156
+ def connection_pool_for(url)
157
+ origin = HTTP::URI.parse(url).origin
158
+ @connection_pools_mutex.synchronize do
159
+ @connection_pools[origin] ||= ConnectionPool.new(size: 3) do
160
+ build_persistent_client(origin)
161
+ end
162
+ end
163
+ end
164
+
165
+ def build_persistent_client(origin)
166
+ client = HTTP.use(:auto_inflate)
167
+ client = client.timeout(@timeout) if @timeout
168
+ client.persistent(origin)
169
+ end
170
+
171
+ def build_request_headers(zipped, event_count, extra_headers, use_statsig_headers)
172
+ headers = {}
173
+
174
+ if use_statsig_headers
175
+ meta = Statsig.get_statsig_metadata
176
+ headers.merge!(
177
+ 'STATSIG-API-KEY' => @server_secret,
178
+ 'STATSIG-SERVER-SESSION-ID' => @session_id,
179
+ 'Content-Type' => 'application/json; charset=UTF-8',
180
+ 'STATSIG-SDK-TYPE' => meta['sdkType'],
181
+ 'STATSIG-SDK-VERSION' => meta['sdkVersion'],
182
+ 'STATSIG-SDK-LANGUAGE-VERSION' => meta['languageVersion'],
183
+ 'Accept' => 'application/json',
184
+ 'Accept-Encoding' => 'gzip',
185
+ 'STATSIG-CLIENT-TIME' => (Time.now.to_f * 1000).to_i.to_s
186
+ )
187
+ end
188
+
189
+ headers['CONTENT-ENCODING'] = 'gzip' if zipped
190
+ headers['STATSIG-EVENT-COUNT'] = event_count.to_s unless event_count == 0
191
+
192
+ headers.merge!(extra_headers)
193
+ headers.delete_if { |_key, value| value.nil? }
194
+ headers
148
195
  end
149
196
  end
150
197
  end
data/lib/spec_store.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require 'concurrent-ruby'
2
- require 'net/http'
3
- require 'uri'
4
2
  require_relative 'api_config'
5
3
  require_relative 'evaluation_details'
6
4
  require_relative 'hash_utils'
@@ -302,14 +300,15 @@ module Statsig
302
300
  if e.nil?
303
301
  unless response.nil?
304
302
  tracker = @diagnostics.track(context, 'download_config_specs', 'process')
305
- failure_details = process_specs(response.body.to_s)
303
+ body = response.body.to_s
304
+ failure_details = process_specs(body)
306
305
  if failure_details.nil?
307
306
  @init_reason = EvaluationReason::NETWORK
308
307
  end
309
308
  tracker.end(success: @init_reason == EvaluationReason::NETWORK)
310
309
 
311
310
  unless response.body.nil? or @rules_updated_callback.nil?
312
- @rules_updated_callback.call(response.body.to_s,
311
+ @rules_updated_callback.call(body,
313
312
  @last_config_sync_time)
314
313
  end
315
314
  end
@@ -413,9 +412,10 @@ module Statsig
413
412
  end
414
413
 
415
414
  begin
416
- server_id_lists = JSON.parse(response)
415
+ body = response.body.to_s
416
+ server_id_lists = JSON.parse(body)
417
417
  process_id_lists(server_id_lists, context)
418
- save_id_lists_to_adapter(response.body.to_s)
418
+ save_id_lists_to_adapter(body)
419
419
  rescue StandardError
420
420
  # Ignored, will try again
421
421
  end
@@ -509,14 +509,18 @@ module Statsig
509
509
 
510
510
  def download_single_id_list(list, context)
511
511
  nil unless list.is_a? IDList
512
- http = HTTP.headers({ 'Range' => "bytes=#{list&.size || 0}-" }).accept(:json)
513
512
  tracker = @diagnostics.track(context, 'get_id_list', 'network_request', { url: list.url })
514
513
  begin
515
- res = http.get(list.url)
516
- tracker.end(statusCode: res.status.code, success: res.status.success?)
517
- nil unless res.status.success?
514
+ res, e = @network.download_id_list(list.url, list&.size || 0)
515
+ code = res&.status&.to_i
516
+ if e.is_a? NetworkError
517
+ code = e.http_code
518
+ end
519
+ success = e.nil? && !res.nil?
520
+ tracker.end(statusCode: code, success: success)
521
+ return nil unless success
518
522
  content_length = Integer(res['content-length'])
519
- nil if content_length.nil? || content_length <= 0
523
+ return nil if content_length.nil? || content_length <= 0
520
524
  content = res.body.to_s
521
525
  success = process_single_id_list(list, context, content, content_length)
522
526
  save_single_id_list_to_adapter(list.name, content) unless success.nil? || !success
data/lib/statsig.rb CHANGED
@@ -410,7 +410,7 @@ module Statsig
410
410
  def self.get_statsig_metadata
411
411
  {
412
412
  'sdkType' => 'ruby-server',
413
- 'sdkVersion' => '2.8.4',
413
+ 'sdkVersion' => '2.9.1',
414
414
  'languageVersion' => RUBY_VERSION
415
415
  }
416
416
  end
@@ -291,8 +291,12 @@ class StatsigDriver
291
291
  def shutdown
292
292
  @err_boundary.capture(caller: __method__) do
293
293
  @shutdown = true
294
- @logger.shutdown
295
- @evaluator.shutdown
294
+ begin
295
+ @logger.shutdown
296
+ @evaluator.shutdown
297
+ ensure
298
+ @net.shutdown
299
+ end
296
300
  end
297
301
  end
298
302
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsig
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.4
4
+ version: 2.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-26 00:00:00.000000000 Z
11
+ date: 2026-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -306,16 +306,22 @@ dependencies:
306
306
  name: zlib
307
307
  requirement: !ruby/object:Gem::Requirement
308
308
  requirements:
309
- - - "~>"
309
+ - - ">="
310
+ - !ruby/object:Gem::Version
311
+ version: '3.1'
312
+ - - "<"
310
313
  - !ruby/object:Gem::Version
311
- version: 3.1.0
314
+ version: '4.0'
312
315
  type: :runtime
313
316
  prerelease: false
314
317
  version_requirements: !ruby/object:Gem::Requirement
315
318
  requirements:
316
- - - "~>"
319
+ - - ">="
320
+ - !ruby/object:Gem::Version
321
+ version: '3.1'
322
+ - - "<"
317
323
  - !ruby/object:Gem::Version
318
- version: 3.1.0
324
+ version: '4.0'
319
325
  description: Statsig server SDK for feature gates and experimentation in Ruby
320
326
  email: support@statsig.com
321
327
  executables: []