Skip to content

Commit 2e3bf09

Browse files
authored
feat(ruby): add wait_for_initialization() method (#247)
1 parent 81aaa5b commit 2e3bf09

File tree

8 files changed

+99
-9
lines changed

8 files changed

+99
-9
lines changed

.changeset/odd-eyes-deny.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ruby-sdk": minor
3+
---
4+
5+
Add `wait_for_initialization()` method.

ruby-sdk/Cargo.lock

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ruby-sdk/ext/eppo_client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ serde = { version = "1.0.203", features = ["derive"] }
1919
serde_magnus = "0.9.0"
2020
rb-sys = "0.9.102"
2121
serde_json = "1.0.128"
22+
tokio = { version = "1.44.1", default-features = false, features = ["time"] }

ruby-sdk/ext/eppo_client/src/client.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,33 @@ impl Client {
238238
serde_magnus::serialize(&result)
239239
}
240240

241+
pub fn wait_for_initialization(&self, timeout_secs: f64) {
242+
log::info!(target: "eppo", "waiting for initialization");
243+
let thread = self.background_thread.borrow();
244+
let Some(thread) = thread.as_ref() else {
245+
log::warn!(target: "eppo", "failed to wait for initialization: background thread is not running");
246+
return;
247+
};
248+
let Some(poller) = &self.configuration_poller else {
249+
log::warn!(target: "eppo", "failed to wait for initialization: configuration poller has not been started");
250+
return;
251+
};
252+
253+
let _ = thread
254+
.runtime()
255+
.async_runtime
256+
.block_on(async {
257+
tokio::time::timeout(
258+
Duration::from_secs_f64(timeout_secs),
259+
poller.wait_for_configuration(),
260+
)
261+
.await
262+
})
263+
.inspect_err(|err| {
264+
log::warn!(target: "eppo", "failed to wait for initialization: {err:?}");
265+
});
266+
}
267+
241268
pub fn get_configuration(&self) -> Option<Configuration> {
242269
self.configuration_store
243270
.get_configuration()

ruby-sdk/ext/eppo_client/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
2929
method!(Client::get_bandit_action_details, 5),
3030
)?;
3131
core_client.define_method("track", method!(Client::track, 2))?;
32+
core_client.define_method(
33+
"wait_for_initialization",
34+
method!(Client::wait_for_initialization, 1),
35+
)?;
3236
core_client.define_method("configuration", method!(Client::get_configuration, 0))?;
3337
core_client.define_method("configuration=", method!(Client::set_configuration, 1))?;
3438
core_client.define_method("shutdown", method!(Client::shutdown, 0))?;

ruby-sdk/lib/eppo_client/client.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ def init(config)
3131
@core = EppoClient::Core::Client.new(config)
3232
end
3333

34+
##
35+
# Waits for client to fetch configuration and get ready to serve
36+
# assignments.
37+
#
38+
# This method blocks the current thread until configuration is
39+
# successfully fetched or +timeout+ seconds passed.
40+
#
41+
# Note: this method returns immediately if configuration poller
42+
# has been disabled.
43+
def wait_for_initialization(timeout=1)
44+
return unless @core
45+
@core.wait_for_initialization(timeout)
46+
end
47+
3448
def configuration
3549
@core.configuration
3650
end

ruby-sdk/spec/eppo_client_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,46 @@
77
expect(EppoClient::VERSION).not_to be nil
88
end
99

10+
describe "wait_for_initialization()" do
11+
before :each do
12+
EppoClient::Client.instance.init(EppoClient::Config.new("test-api-key", base_url: "http://127.0.0.1:8378/ufc/api"))
13+
@client = EppoClient::Client.instance
14+
end
15+
16+
it "has default timeout" do
17+
@client.wait_for_initialization()
18+
19+
expect(@client.configuration).not_to be_nil
20+
end
21+
22+
it "allows 0 timeout" do
23+
@client.wait_for_initialization(0)
24+
25+
# Local configuration fetching is so fast that sometimes it
26+
# succeeds before we call wait_for_initialization(). Therefore,
27+
# we treat absense of errors as a sign of success here.
28+
end
29+
30+
it "allows fractional timeout" do
31+
@client.wait_for_initialization(0.5)
32+
33+
expect(@client.configuration).not_to be_nil
34+
end
35+
36+
it "timeouts with bad URL" do
37+
EppoClient::Client.instance.init(EppoClient::Config.new("test-api-key", base_url: "http://127.0.0.1:8378/undefined/api"))
38+
@client = EppoClient::Client.instance
39+
40+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
41+
@client.wait_for_initialization(0.2)
42+
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
43+
44+
expect(@client.configuration).to be_nil
45+
# shall wait at least for timeout
46+
expect(stop - start).to be >= 0.2
47+
end
48+
end
49+
1050
describe "configuration()" do
1151
it "allows getting configuration" do
1252
init_client_for "ufc"

ruby-sdk/spec/spec_helper.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ def init_client_for(test_name)
88
else
99
config = EppoClient::Config.new("test-api-key", base_url: "http://127.0.0.1:8378/#{test_name}/api")
1010
EppoClient::Client.instance.init(config)
11-
12-
# Sleep to allow the client to fetch config
13-
sleep(0.050)
11+
EppoClient::Client.instance.wait_for_initialization()
1412
end
1513
end
1614

0 commit comments

Comments
 (0)