Skip to content

Commit

Permalink
feat: Use last modified headers (#108)
Browse files Browse the repository at this point in the history
* Use last modified headers

* Update tests and WASM

* Update min ruby version

* Update Matrix to use ruby 3.1 as min.

* update signature

* Still save if last modified header is empty and no config is saved

* ci-testing

* Setup try/catch for parsing headers

* Make sure valid date format for header
  • Loading branch information
JamieSinn authored Jul 17, 2024
1 parent 536c967 commit 9371f48
Show file tree
Hide file tree
Showing 11 changed files with 55 additions and 33 deletions.
12 changes: 0 additions & 12 deletions .github/dependabot.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: [ '3.0', '3.0.4', '3.1.0', '3.2.0' ]
ruby-version: [ '3.1.0', '3.2.0', '3.3.0' ]

steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@v1.187.0
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
source 'https://rubygems.org'

gemspec
gem 'sorbet-runtime'
gem 'sorbet-runtime', '0.5.11481'
gem 'oj'
gem 'wasmtime'
gem 'concurrent-ruby'
Expand Down
6 changes: 3 additions & 3 deletions devcycle-ruby-server-sdk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ Gem::Specification.new do |s|
s.summary = "DevCycle Bucketing API Ruby Gem"
s.description = "DevCycle Ruby Server SDK, for interacting with feature flags created with the DevCycle platform."
s.license = "MIT"
s.required_ruby_version = ">= 2.4"
s.required_ruby_version = ">= 3.1"

s.add_runtime_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
s.add_runtime_dependency 'wasmtime', '13.0.0'
s.add_runtime_dependency 'wasmtime', '20.0.2'
s.add_runtime_dependency 'concurrent-ruby', '~> 1.2.0'
s.add_runtime_dependency 'sorbet-runtime', '~> 0.5'
s.add_runtime_dependency 'sorbet-runtime', '>= 0.5.11481'
s.add_runtime_dependency 'oj', '~> 3.0'
s.add_runtime_dependency 'google-protobuf', '~> 3.22'

Expand Down
4 changes: 2 additions & 2 deletions lib/devcycle-ruby-server-sdk/api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ def variable_value(user, key, default, opts = {})
end

# Get variable by key for user data
# @param user [User]
# @param user [DevCycle::User]
# @param key [String] Variable key
# @param default Default value for variable if none is retrieved
# @param [Hash] opts the optional parameters
# @return [Variable]
def variable(user, key, default, opts = {})
if !user.is_a?(DevCycle::User)
unless user.is_a?(DevCycle::User)
fail ArgumentError, "user param must be an instance of DevCycle::User!"
end

Expand Down
Binary file not shown.
40 changes: 32 additions & 8 deletions lib/devcycle-ruby-server-sdk/localbucketing/config_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'concurrent-ruby'
require 'typhoeus'
require 'json'
require 'time'

module DevCycle
class ConfigManager
Expand All @@ -19,13 +20,14 @@ def initialize(sdkKey, local_bucketing, wait_for_init)
@local_bucketing = local_bucketing
@sdkKey = sdkKey
@config_e_tag = ""
@config_last_modified = ""
@logger = local_bucketing.options.logger
@polling_enabled = true
@max_config_retries = 2

@config_poller = Concurrent::TimerTask.new({
execution_interval: @local_bucketing.options.config_polling_interval_ms.fdiv(1000)
}) do |task|
execution_interval: @local_bucketing.options.config_polling_interval_ms.fdiv(1000)
}) do |task|
fetch_config
end

Expand Down Expand Up @@ -53,22 +55,43 @@ def fetch_config
Accept: "application/json",
})

begin
Date.parse(@config_last_modified)
if @config_last_modified != ""
req.options[:headers]["If-Modified-Since"] = Time.httpdate(@config_last_modified)
end
rescue
end


if @config_e_tag != ""
req.options[:headers]['If-None-Match'] = @config_e_tag
end

@max_config_retries.times do
@logger.debug("Requesting new config from #{get_config_url}, current etag: #{@config_e_tag}")
@logger.debug("Requesting new config from #{get_config_url}, current etag: #{@config_e_tag}, last modified: #{@config_last_modified}")
resp = req.run
@logger.debug("Config request complete, status: #{resp.code}")
case resp.code
when 304
@logger.debug("Config not modified, using cache, etag: #{@config_e_tag}")
@logger.debug("Config not modified, using cache, etag: #{@config_e_tag}, last modified: #{@config_last_modified}")
break
when 200
@logger.debug("New config received, etag: #{resp.headers['Etag']}")
set_config(resp.body, resp.headers['Etag'])
@logger.debug("New config stored, etag: #{@config_e_tag}")
@logger.debug("New config received, etag: #{resp.headers['Etag']} LM:#{resp.headers['Last-Modified']}")
lm_header = resp.headers['Last-Modified']
begin
lm_timestamp = Time.rfc2822(lm_header)
current_lm = Time.rfc2822(@config_last_modified)
if lm_timestamp == "" && @config_last_modified == "" || (current_lm.utc < lm_timestamp.utc)
set_config(resp.body, resp.headers['Etag'], lm_header)
@logger.debug("New config stored, etag: #{@config_e_tag}, last modified: #{lm_header}")
else
@logger.warn("Config response was an older config than currently stored config.")
end
rescue
@logger.warn("Failed to parse last modified header, setting config.")
set_config(resp.body, resp.headers['Etag'], lm_header)
end
break
when 403
stop_polling
Expand All @@ -91,13 +114,14 @@ def fetch_config
nil
end

def set_config(config, etag)
def set_config(config, etag, lastmodified)
if !JSON.parse(config).is_a?(Hash)
raise("Invalid JSON body parsed from Config Response")
end

@local_bucketing.store_config(config)
@config_e_tag = etag
@config_last_modified = lastmodified
@local_bucketing.has_config = true
end

Expand Down
5 changes: 4 additions & 1 deletion lib/devcycle-ruby-server-sdk/localbucketing/event_queue.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'typhoeus'
require 'sorbet-runtime'
require 'concurrent-ruby'
require 'securerandom'


module DevCycle
class EventQueue
Expand All @@ -9,6 +11,7 @@ class EventQueue
sig { params(sdkKey: String, options: EventQueueOptions, local_bucketing: LocalBucketing).void }
def initialize(sdkKey, options, local_bucketing)
@sdkKey = sdkKey
@client_uuid = SecureRandom.uuid
@events_api_uri = options.events_api_uri
@logger = options.logger
@event_flush_interval_ms = options.event_flush_interval_ms
Expand All @@ -22,7 +25,7 @@ def initialize(sdkKey, options, local_bucketing)
@flush_timer_task.execute
@flush_mutex = Mutex.new
@local_bucketing = local_bucketing
@local_bucketing.init_event_queue(options)
@local_bucketing.init_event_queue(@client_uuid, options)
end

def close
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class LocalBucketing
.inherit_stderr
.set_argv(ARGV)
.set_env(ENV)
.build
@@store = Wasmtime::Store.new(@@engine, wasi_ctx: @@wasi_ctx)
@@linker = Wasmtime::Linker.new(@@engine, wasi: true)

Expand Down Expand Up @@ -245,13 +246,14 @@ def store_config(config)
end
end

sig { params(options: EventQueueOptions).returns(NilClass) }
def init_event_queue(options)
sig { params(client_uuid: String, options: EventQueueOptions).returns(NilClass) }
def init_event_queue(client_uuid, options)
@wasm_mutex.synchronize do
options_json = Oj.dump(options)
client_uuid_addr = malloc_asc_string(client_uuid)
options_addr = malloc_asc_string(options_json)
@@stack_tracer = @@stack_tracer_raise
@@instance.invoke("initEventQueue", @sdkKeyAddr, options_addr)
@@instance.invoke("initEventQueue", @sdkKeyAddr, client_uuid_addr, options_addr)
end
end

Expand Down
5 changes: 5 additions & 0 deletions lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
BUCKETING_LIB_VERSION="1.24.2"
WAT_DOWNLOAD=0
rm bucketing-lib.release.wasm
wget "https://unpkg.com/@devcycle/bucketing-assembly-script@$BUCKETING_LIB_VERSION/build/bucketing-lib.release.wasm"
2 changes: 1 addition & 1 deletion spec/api/devcycle_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
it 'should work' do
result = @api_instance.all_variables(@user)

expect(result.length).to eq 1
expect(result.length).to eq 5
end
end

Expand Down

0 comments on commit 9371f48

Please sign in to comment.