statsig 1.22.0 → 1.23.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87f81b59161aaf7f035b0822858db6528c4268dddddaa46f012b6da61017c366
4
- data.tar.gz: 0c4b7ce02036994d5f3afcf1e1ecda13df7a3541b0bb1f6441996842cd91614f
3
+ metadata.gz: 89aaef291fd8cbd82af8bcf380894ea554588988b2de66507482420b475c8367
4
+ data.tar.gz: 3f535a1ce2a9af6e811c77df334b3b58a3c2503857f74bbf5b280764cfb041f1
5
5
  SHA512:
6
- metadata.gz: 344af0408c2136fa81acfe1f30258cd30b9ded6c472d0c1092583888793f9b998c303c77a86c99ff7484ceab6e3f2bdf69cf02ba17160208ca5ea2cbece562a0
7
- data.tar.gz: 75a9cd2967a25295b062f68279a7e6b0e0da4e59d7aeacaafbd9261605dce4ee9795633b9631c00008bace9395350210978b882edcc0364ecc3a1b80883c819c
6
+ metadata.gz: 16074e02fd22c2d0bc64e8adbd4b2551d46509e60f161b9ff5009ea28e13a995f13e0abd89c48c1c737d80dad1a869b8f15de8ac2b896a47ad6da54cdadbdd70
7
+ data.tar.gz: ac6e8241c8132844a0ee5e8858578ab3475cb227e48847fb840d4ef15219828f58c38f24d2c2e5e2bfe26714bda07f8c1068f9a15af675d4320e4a90c6c2a7c6
@@ -3,6 +3,7 @@ module Statsig
3
3
  module Interfaces
4
4
  class IDataStore
5
5
  CONFIG_SPECS_KEY = "statsig.cache"
6
+ ID_LISTS_KEY = "statsig.id_lists"
6
7
 
7
8
  def init
8
9
  end
data/lib/spec_store.rb CHANGED
@@ -42,7 +42,7 @@ module Statsig
42
42
  puts 'data_store gets priority over bootstrap_values. bootstrap_values will be ignored'
43
43
  else
44
44
  init_diagnostics&.mark("bootstrap", "start", "load")
45
- if process(options.bootstrap_values)
45
+ if process_specs(options.bootstrap_values)
46
46
  @init_reason = EvaluationReason::BOOTSTRAP
47
47
  end
48
48
  init_diagnostics&.mark("bootstrap", "end", "load", @init_reason == EvaluationReason::BOOTSTRAP)
@@ -55,7 +55,7 @@ module Statsig
55
55
  unless @options.data_store.nil?
56
56
  init_diagnostics&.mark("data_store", "start", "load")
57
57
  @options.data_store.init
58
- load_from_storage_adapter
58
+ load_config_specs_from_storage_adapter(init_diagnostics: init_diagnostics)
59
59
  init_diagnostics&.mark("data_store", "end", "load", @init_reason == EvaluationReason::DATA_ADAPTER)
60
60
  end
61
61
 
@@ -64,7 +64,11 @@ module Statsig
64
64
  end
65
65
 
66
66
  @initial_config_sync_time = @last_config_sync_time == 0 ? -1 : @last_config_sync_time
67
- get_id_lists(init_diagnostics)
67
+ if !@options.data_store.nil?
68
+ get_id_lists_from_adapter(init_diagnostics)
69
+ else
70
+ get_id_lists_from_network(init_diagnostics)
71
+ end
68
72
 
69
73
  @config_sync_thread = sync_config_specs
70
74
  @id_lists_sync_thread = sync_id_lists
@@ -130,16 +134,23 @@ module Statsig
130
134
 
131
135
  private
132
136
 
133
- def load_from_storage_adapter
137
+ def load_config_specs_from_storage_adapter(init_diagnostics: nil)
138
+ init_diagnostics&.mark("download_config_specs", "start", "fetch_from_adapter")
134
139
  cached_values = @options.data_store.get(Interfaces::IDataStore::CONFIG_SPECS_KEY)
135
- if cached_values.nil?
136
- return
137
- end
138
- process(cached_values, true)
140
+ init_diagnostics&.mark("download_config_specs", "end", "fetch_from_adapter", true)
141
+ return if cached_values.nil?
142
+
143
+ init_diagnostics&.mark("download_config_specs", "start", "process")
144
+ process_specs(cached_values, from_adapter: true)
139
145
  @init_reason = EvaluationReason::DATA_ADAPTER
146
+ init_diagnostics&.mark("download_config_specs", "end", "process", @init_reason)
147
+ rescue StandardError
148
+ # Fallback to network
149
+ init_diagnostics&.mark("download_config_specs", "end", "fetch_from_adapter", false)
150
+ download_config_specs(init_diagnostics)
140
151
  end
141
152
 
142
- def save_to_storage_adapter(specs_string)
153
+ def save_config_specs_to_storage_adapter(specs_string)
143
154
  if @options.data_store.nil?
144
155
  return
145
156
  end
@@ -151,7 +162,7 @@ module Statsig
151
162
  loop do
152
163
  sleep @options.rulesets_sync_interval
153
164
  if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::CONFIG_SPECS_KEY)
154
- load_from_storage_adapter
165
+ load_config_specs_from_storage_adapter
155
166
  else
156
167
  download_config_specs
157
168
  end
@@ -163,7 +174,11 @@ module Statsig
163
174
  Thread.new do
164
175
  loop do
165
176
  sleep @id_lists_sync_interval
166
- get_id_lists
177
+ if @options.data_store&.should_be_used_for_querying_updates(Interfaces::IDataStore::ID_LISTS_KEY)
178
+ get_id_lists_from_adapter
179
+ else
180
+ get_id_lists_from_network
181
+ end
167
182
  end
168
183
  end
169
184
  end
@@ -184,7 +199,7 @@ module Statsig
184
199
  unless response.nil?
185
200
  init_diagnostics&.mark("download_config_specs", "start", "process")
186
201
 
187
- if process(response.body)
202
+ if process_specs(response.body)
188
203
  @init_reason = EvaluationReason::NETWORK
189
204
  @rules_updated_callback.call(response.body.to_s, @last_config_sync_time) unless response.body.nil? or @rules_updated_callback.nil?
190
205
  end
@@ -203,7 +218,7 @@ module Statsig
203
218
  @error_callback.call(error) unless error.nil? or @error_callback.nil?
204
219
  end
205
220
 
206
- def process(specs_string, from_adapter = false)
221
+ def process_specs(specs_string, from_adapter: false)
207
222
  if specs_string.nil?
208
223
  return false
209
224
  end
@@ -238,12 +253,33 @@ module Statsig
238
253
  @specs[:experiment_to_layer] = new_exp_to_layer
239
254
 
240
255
  unless from_adapter
241
- save_to_storage_adapter(specs_string)
256
+ save_config_specs_to_storage_adapter(specs_string)
242
257
  end
243
258
  true
244
259
  end
245
260
 
246
- def get_id_lists(init_diagnostics = nil)
261
+ def get_id_lists_from_adapter(init_diagnostics = nil)
262
+ init_diagnostics&.mark("get_id_lists", "start", "fetch_from_adapter")
263
+ cached_values = @options.data_store.get(Interfaces::IDataStore::ID_LISTS_KEY)
264
+ return if cached_values.nil?
265
+
266
+ init_diagnostics&.mark("get_id_lists", "end", "fetch_from_adapter", true)
267
+ id_lists = JSON.parse(cached_values)
268
+ process_id_lists(id_lists, init_diagnostics, from_adapter: true)
269
+ rescue StandardError
270
+ # Fallback to network
271
+ init_diagnostics&.mark("get_id_lists", "end", "fetch_from_adapter", false)
272
+ get_id_lists_from_network(init_diagnostics)
273
+ end
274
+
275
+ def save_id_lists_to_adapter(id_lists)
276
+ if @options.data_store.nil?
277
+ return
278
+ end
279
+ @options.data_store.set(Interfaces::IDataStore::CONFIG_SPECS_KEY, JSON.generate(id_lists))
280
+ end
281
+
282
+ def get_id_lists_from_network(init_diagnostics = nil)
247
283
  init_diagnostics&.mark("get_id_lists", "start", "network_request")
248
284
  response, e = @network.post_helper('get_id_lists', JSON.generate({ 'statsigMetadata' => Statsig.get_statsig_metadata }))
249
285
  if !e.nil? || response.nil?
@@ -253,69 +289,91 @@ module Statsig
253
289
 
254
290
  begin
255
291
  server_id_lists = JSON.parse(response)
256
- local_id_lists = @specs[:id_lists]
257
- if !server_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
258
- return
259
- end
260
- tasks = []
261
-
262
- if server_id_lists.length == 0
263
- return
264
- end
292
+ process_id_lists(server_id_lists, init_diagnostics)
293
+ rescue
294
+ # Ignored, will try again
295
+ end
296
+ end
265
297
 
266
- init_diagnostics&.mark("get_id_lists", "start", "process", server_id_lists.length)
298
+ def process_id_lists(new_id_lists, init_diagnostics, from_adapter: false)
299
+ local_id_lists = @specs[:id_lists]
300
+ if !new_id_lists.is_a?(Hash) || !local_id_lists.is_a?(Hash)
301
+ return
302
+ end
303
+ tasks = []
267
304
 
268
- server_id_lists.each do |list_name, list|
269
- server_list = IDList.new(list)
270
- local_list = get_id_list(list_name)
305
+ if new_id_lists.length == 0
306
+ return
307
+ end
271
308
 
272
- unless local_list.is_a? IDList
273
- local_list = IDList.new(list)
274
- local_list.size = 0
275
- local_id_lists[list_name] = local_list
276
- end
309
+ init_diagnostics&.mark("get_id_lists", "start", "process", new_id_lists.length)
277
310
 
278
- # skip if server list is invalid
279
- if server_list.url.nil? || server_list.creation_time < local_list.creation_time || server_list.file_id.nil?
280
- next
281
- end
311
+ new_id_lists.each do |list_name, list|
312
+ new_list = IDList.new(list)
313
+ local_list = get_id_list(list_name)
282
314
 
283
- # reset local list if server list returns a newer file
284
- if server_list.file_id != local_list.file_id && server_list.creation_time >= local_list.creation_time
285
- local_list = IDList.new(list)
286
- local_list.size = 0
287
- local_id_lists[list_name] = local_list
288
- end
315
+ unless local_list.is_a? IDList
316
+ local_list = IDList.new(list)
317
+ local_list.size = 0
318
+ local_id_lists[list_name] = local_list
319
+ end
289
320
 
290
- # skip if server list is no bigger than local list, which means nothing new to read
291
- if server_list.size <= local_list.size
292
- next
293
- end
321
+ # skip if server list is invalid
322
+ if new_list.url.nil? || new_list.creation_time < local_list.creation_time || new_list.file_id.nil?
323
+ next
324
+ end
294
325
 
295
- tasks << Concurrent::Promise.execute(:executor => @id_list_thread_pool) do
296
- download_single_id_list(local_list)
297
- end
326
+ # reset local list if server list returns a newer file
327
+ if new_list.file_id != local_list.file_id && new_list.creation_time >= local_list.creation_time
328
+ local_list = IDList.new(list)
329
+ local_list.size = 0
330
+ local_id_lists[list_name] = local_list
298
331
  end
299
332
 
300
- result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
301
- if result.state != :fulfilled
302
- init_diagnostics&.mark("get_id_lists", "end", "process", false)
303
- return # timed out
333
+ # skip if server list is no bigger than local list, which means nothing new to read
334
+ if new_list.size <= local_list.size
335
+ next
304
336
  end
305
337
 
306
- delete_lists = []
307
- local_id_lists.each do |list_name, list|
308
- unless server_id_lists.key? list_name
309
- delete_lists.push list_name
338
+ tasks << Concurrent::Promise.execute(:executor => @id_list_thread_pool) do
339
+ if from_adapter
340
+ get_single_id_list_from_adapter(local_list)
341
+ else
342
+ download_single_id_list(local_list)
310
343
  end
311
344
  end
312
- delete_lists.each do |list_name|
313
- local_id_lists.delete list_name
345
+ end
346
+
347
+ result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
348
+ if result.state != :fulfilled
349
+ init_diagnostics&.mark("get_id_lists", "end", "process", false)
350
+ return # timed out
351
+ end
352
+
353
+ delete_lists = []
354
+ local_id_lists.each do |list_name, list|
355
+ unless new_id_lists.key? list_name
356
+ delete_lists.push list_name
314
357
  end
315
- init_diagnostics&.mark("get_id_lists", "end", "process", true)
316
- rescue
317
- # Ignored, will try again
318
358
  end
359
+ delete_lists.each do |list_name|
360
+ local_id_lists.delete list_name
361
+ end
362
+ init_diagnostics&.mark("get_id_lists", "end", "process", true)
363
+ end
364
+
365
+ def get_single_id_list_from_adapter(list)
366
+ cached_values = @options.data_store.get("#{Interfaces::IDataStore::ID_LISTS_KEY}::#{list.name}")
367
+ content = cached_values.to_s
368
+ process_single_id_list(list, content)
369
+ rescue StandardError
370
+ nil
371
+ end
372
+
373
+ def save_single_id_list_to_adapter(name, content)
374
+ return if @options.data_store.nil?
375
+
376
+ @options.data_store.set("#{Interfaces::IDataStore::ID_LISTS_KEY}::#{name}", content)
319
377
  end
320
378
 
321
379
  def download_single_id_list(list)
@@ -327,9 +385,19 @@ module Statsig
327
385
  content_length = Integer(res['content-length'])
328
386
  nil if content_length.nil? || content_length <= 0
329
387
  content = res.body.to_s
388
+ success = process_single_id_list(list, content, content_length)
389
+ save_single_id_list_to_adapter(list.name, content) unless success.nil? || !success
390
+ rescue
391
+ nil
392
+ end
393
+ end
394
+
395
+ def process_single_id_list(list, content, content_length = nil)
396
+ false unless list.is_a? IDList
397
+ begin
330
398
  unless content.is_a?(String) && (content[0] == '-' || content[0] == '+')
331
399
  @specs[:id_lists].delete(list.name)
332
- return
400
+ return false
333
401
  end
334
402
  ids_clone = list.ids # clone the list, operate on the new list, and swap out the old list, so the operation is thread-safe
335
403
  lines = content.split(/\r?\n/)
@@ -345,9 +413,14 @@ module Statsig
345
413
  end
346
414
  end
347
415
  list.ids = ids_clone
348
- list.size = list.size + content_length
416
+ list.size = if content_length.nil?
417
+ list.size + content.bytesize
418
+ else
419
+ list.size + content_length
420
+ end
421
+ return true
349
422
  rescue
350
- nil
423
+ return false
351
424
  end
352
425
  end
353
426
  end
data/lib/statsig.rb CHANGED
@@ -227,7 +227,7 @@ module Statsig
227
227
  def self.get_statsig_metadata
228
228
  {
229
229
  'sdkType' => 'ruby-server',
230
- 'sdkVersion' => '1.21.0',
230
+ 'sdkVersion' => '1.23.0',
231
231
  }
232
232
  end
233
233
 
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: 1.22.0
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-02 00:00:00.000000000 Z
11
+ date: 2023-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler