Skip to content

Commit

Permalink
feat: Add wait_for_initialization with timeout parameter (#76)
Browse files Browse the repository at this point in the history
This method serves as a replacement for the `initialized_async` method,
which has now been deprecated. LaunchDarkly does not recommend blocking
an application indefinitely, and so we are working to remove methods
that suggest this behavior.

Co-authored-by: Casey Waldren <[email protected]>
  • Loading branch information
keelerm84 and cwaldren-ld authored May 20, 2024
1 parent 0c1c58d commit 45e3451
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 4 deletions.
2 changes: 1 addition & 1 deletion contract-tests/src/client_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl ClientEntity {
let config = config_builder.build()?;
let client = Client::build(config)?;
client.start_with_default_executor();
client.initialized_async().await;
client.wait_for_initialization(Duration::from_secs(5)).await;

Ok(Self { client })
}
Expand Down
5 changes: 4 additions & 1 deletion launchdarkly-server-sdk/examples/print_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ async fn main() {

let mut interval = time::interval(Duration::from_secs(5));

let initialized = client.initialized_async().await;
let initialized = client
.wait_for_initialization(Duration::from_secs(5))
.await
.unwrap_or(false); // A timeout (None) can be treated as initialization failure

if !initialized {
error!("The client failed to initialize!");
Expand Down
61 changes: 59 additions & 2 deletions launchdarkly-server-sdk/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use parking_lot::RwLock;
use std::io;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;

use launchdarkly_server_sdk_evaluation::{self as eval, Detail, FlagValue, PrerequisiteEvent};
Expand Down Expand Up @@ -270,7 +271,31 @@ impl Client {
/// This is an async method that will resolve once initialization is complete.
/// Initialization being complete does not mean that initialization was a success.
/// The return value from the method indicates if the client successfully initialized.
#[deprecated(
note = "blocking without a timeout is discouraged, use wait_for_initialization instead"
)]
pub async fn initialized_async(&self) -> bool {
self.initialized_async_internal().await
}

/// This is an async method that will resolve once initialization is complete or the specified
/// timeout has occurred.
///
/// If the timeout is triggered, this method will return `None`. Otherwise, the method will
/// return a boolean indicating whether or not the SDK has successfully initialized.
pub async fn wait_for_initialization(&self, timeout: Duration) -> Option<bool> {
if timeout > Duration::from_secs(60) {
warn!("wait_for_initialization was configured to block for up to {} seconds. We recommend blocking no longer than 60 seconds.", timeout.as_secs());
}

let initialized = tokio::time::timeout(timeout, self.initialized_async_internal()).await;
match initialized {
Ok(result) => Some(result),
Err(_) => None,
}
}

async fn initialized_async_internal(&self) -> bool {
if self.offline {
return true;
}
Expand Down Expand Up @@ -789,6 +814,36 @@ mod tests {
assert!(elapsed_time.as_millis() > 500)
}

#[tokio::test]
async fn client_asynchronously_initializes_within_timeout() {
let (client, _event_rx) = make_mocked_client_with_delay(1000, false);
client.start_with_default_executor();

let now = Instant::now();
let initialized = client
.wait_for_initialization(Duration::from_millis(1500))
.await;
let elapsed_time = now.elapsed();
// Give ourself a good margin for thread scheduling.
assert!(elapsed_time.as_millis() > 500);
assert_eq!(initialized, Some(true));
}

#[tokio::test]
async fn client_asynchronously_initializes_slower_than_timeout() {
let (client, _event_rx) = make_mocked_client_with_delay(2000, false);
client.start_with_default_executor();

let now = Instant::now();
let initialized = client
.wait_for_initialization(Duration::from_millis(500))
.await;
let elapsed_time = now.elapsed();
// Give ourself a good margin for thread scheduling.
assert!(elapsed_time.as_millis() < 750);
assert!(initialized.is_none());
}

#[tokio::test]
async fn client_initializes_immediately_in_offline_mode() {
let (client, _event_rx) = make_mocked_client_with_delay(1000, true);
Expand All @@ -797,9 +852,11 @@ mod tests {
assert!(client.initialized());

let now = Instant::now();
let initialized = client.initialized_async().await;
let initialized = client
.wait_for_initialization(Duration::from_millis(2000))
.await;
let elapsed_time = now.elapsed();
assert!(initialized);
assert_eq!(initialized, Some(true));
assert!(elapsed_time.as_millis() < 500)
}

Expand Down

0 comments on commit 45e3451

Please sign in to comment.