@@ -155,6 +155,7 @@ pub struct Client {
155155 init_state : Arc < AtomicUsize > ,
156156 started : AtomicBool ,
157157 offline : bool ,
158+ daemon_mode : bool ,
158159 sdk_key : String ,
159160 shutdown_broadcast : broadcast:: Sender < ( ) > ,
160161 runtime : RwLock < Option < Runtime > > ,
@@ -165,6 +166,8 @@ impl Client {
165166 pub fn build ( config : Config ) -> Result < Self , BuildError > {
166167 if config. offline ( ) {
167168 info ! ( "Started LaunchDarkly Client in offline mode" ) ;
169+ } else if config. daemon_mode ( ) {
170+ info ! ( "Started LaunchDarkly Client in daemon mode" ) ;
168171 }
169172
170173 let tags = config. application_tag ( ) ;
@@ -210,6 +213,7 @@ impl Client {
210213 init_state : Arc :: new ( AtomicUsize :: new ( ClientInitState :: Initializing as usize ) ) ,
211214 started : AtomicBool :: new ( false ) ,
212215 offline : config. offline ( ) ,
216+ daemon_mode : config. daemon_mode ( ) ,
213217 sdk_key : config. sdk_key ( ) . into ( ) ,
214218 shutdown_broadcast : shutdown_tx,
215219 runtime : RwLock :: new ( None ) ,
@@ -297,7 +301,7 @@ impl Client {
297301 }
298302
299303 async fn initialized_async_internal ( & self ) -> bool {
300- if self . offline {
304+ if self . offline || self . daemon_mode {
301305 return true ;
302306 }
303307
@@ -316,7 +320,9 @@ impl Client {
316320 /// In the case of unrecoverable errors in establishing a connection it is possible for the
317321 /// SDK to never become initialized.
318322 pub fn initialized ( & self ) -> bool {
319- self . offline || ClientInitState :: Initialized == self . init_state . load ( Ordering :: SeqCst )
323+ self . offline
324+ || self . daemon_mode
325+ || ClientInitState :: Initialized == self . init_state . load ( Ordering :: SeqCst )
320326 }
321327
322328 /// Close shuts down the LaunchDarkly client. After calling this, the LaunchDarkly client
@@ -325,9 +331,9 @@ impl Client {
325331 pub fn close ( & self ) {
326332 self . event_processor . close ( ) ;
327333
328- // If the system is in offline mode, no receiver will be listening to this broadcast
329- // channel, so sending on it would always result in an error.
330- if !self . offline {
334+ // If the system is in offline mode or daemon mode , no receiver will be listening to this
335+ // broadcast channel, so sending on it would always result in an error.
336+ if !self . offline && ! self . daemon_mode {
331337 if let Err ( e) = self . shutdown_broadcast . send ( ( ) ) {
332338 error ! ( "Failed to shutdown client appropriately: {}" , e) ;
333339 }
@@ -844,7 +850,8 @@ mod tests {
844850 use eval:: { ContextBuilder , MultiContextBuilder } ;
845851 use futures:: FutureExt ;
846852 use hyper:: client:: HttpConnector ;
847- use launchdarkly_server_sdk_evaluation:: Reason ;
853+ use launchdarkly_server_sdk_evaluation:: { Flag , Reason , Segment } ;
854+ use maplit:: hashmap;
848855 use std:: collections:: HashMap ;
849856 use tokio:: time:: Instant ;
850857
@@ -853,12 +860,17 @@ mod tests {
853860 use crate :: events:: create_event_sender;
854861 use crate :: events:: event:: { OutputEvent , VariationKey } ;
855862 use crate :: events:: processor_builders:: EventProcessorBuilder ;
863+ use crate :: stores:: persistent_store:: tests:: InMemoryPersistentDataStore ;
856864 use crate :: stores:: store_types:: { PatchTarget , StorageItem } ;
857865 use crate :: test_common:: {
858866 self , basic_flag, basic_flag_with_prereq, basic_flag_with_prereqs_and_visibility,
859867 basic_flag_with_visibility, basic_int_flag, basic_migration_flag, basic_off_flag,
860868 } ;
861- use crate :: { ConfigBuilder , MigratorBuilder , Operation , Origin } ;
869+ use crate :: {
870+ AllData , ConfigBuilder , MigratorBuilder , NullEventProcessorBuilder , Operation , Origin ,
871+ PersistentDataStore , PersistentDataStoreBuilder , PersistentDataStoreFactory ,
872+ SerializedItem ,
873+ } ;
862874 use test_case:: test_case;
863875
864876 use super :: * ;
@@ -872,7 +884,7 @@ mod tests {
872884
873885 #[ tokio:: test]
874886 async fn client_asynchronously_initializes ( ) {
875- let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , false ) ;
887+ let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , false , false ) ;
876888 client. start_with_default_executor ( ) ;
877889
878890 let now = Instant :: now ( ) ;
@@ -885,7 +897,7 @@ mod tests {
885897
886898 #[ tokio:: test]
887899 async fn client_asynchronously_initializes_within_timeout ( ) {
888- let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , false ) ;
900+ let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , false , false ) ;
889901 client. start_with_default_executor ( ) ;
890902
891903 let now = Instant :: now ( ) ;
@@ -900,7 +912,7 @@ mod tests {
900912
901913 #[ tokio:: test]
902914 async fn client_asynchronously_initializes_slower_than_timeout ( ) {
903- let ( client, _event_rx) = make_mocked_client_with_delay ( 2000 , false ) ;
915+ let ( client, _event_rx) = make_mocked_client_with_delay ( 2000 , false , false ) ;
904916 client. start_with_default_executor ( ) ;
905917
906918 let now = Instant :: now ( ) ;
@@ -915,7 +927,23 @@ mod tests {
915927
916928 #[ tokio:: test]
917929 async fn client_initializes_immediately_in_offline_mode ( ) {
918- let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , true ) ;
930+ let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , true , false ) ;
931+ client. start_with_default_executor ( ) ;
932+
933+ assert ! ( client. initialized( ) ) ;
934+
935+ let now = Instant :: now ( ) ;
936+ let initialized = client
937+ . wait_for_initialization ( Duration :: from_millis ( 2000 ) )
938+ . await ;
939+ let elapsed_time = now. elapsed ( ) ;
940+ assert_eq ! ( initialized, Some ( true ) ) ;
941+ assert ! ( elapsed_time. as_millis( ) < 500 )
942+ }
943+
944+ #[ tokio:: test]
945+ async fn client_initializes_immediately_in_daemon_mode ( ) {
946+ let ( client, _event_rx) = make_mocked_client_with_delay ( 1000 , false , true ) ;
919947 client. start_with_default_executor ( ) ;
920948
921949 assert ! ( client. initialized( ) ) ;
@@ -1393,6 +1421,111 @@ mod tests {
13931421 assert_eq ! ( event_rx. iter( ) . count( ) , 0 ) ;
13941422 }
13951423
1424+ struct InMemoryPersistentDataStoreFactory {
1425+ data : AllData < Flag , Segment > ,
1426+ initialized : bool ,
1427+ }
1428+
1429+ impl PersistentDataStoreFactory for InMemoryPersistentDataStoreFactory {
1430+ fn create_persistent_data_store (
1431+ & self ,
1432+ ) -> Result < Box < ( dyn PersistentDataStore + ' static ) > , std:: io:: Error > {
1433+ let serialized_data =
1434+ AllData :: < SerializedItem , SerializedItem > :: try_from ( self . data . clone ( ) ) ?;
1435+ Ok ( Box :: new ( InMemoryPersistentDataStore {
1436+ data : serialized_data,
1437+ initialized : self . initialized ,
1438+ } ) )
1439+ }
1440+ }
1441+
1442+ #[ test]
1443+ fn variation_detail_handles_daemon_mode ( ) {
1444+ testing_logger:: setup ( ) ;
1445+ let factory = InMemoryPersistentDataStoreFactory {
1446+ data : AllData {
1447+ flags : hashmap ! [ "flag" . into( ) => basic_flag( "flag" ) ] ,
1448+ segments : HashMap :: new ( ) ,
1449+ } ,
1450+ initialized : true ,
1451+ } ;
1452+ let builder = PersistentDataStoreBuilder :: new ( Arc :: new ( factory) ) ;
1453+
1454+ let config = ConfigBuilder :: new ( "sdk-key" )
1455+ . daemon_mode ( true )
1456+ . data_store ( & builder)
1457+ . event_processor ( & NullEventProcessorBuilder :: new ( ) )
1458+ . build ( )
1459+ . expect ( "config should build" ) ;
1460+
1461+ let client = Client :: build ( config) . expect ( "Should be built." ) ;
1462+
1463+ client. start_with_default_executor ( ) ;
1464+
1465+ let context = ContextBuilder :: new ( "bob" )
1466+ . build ( )
1467+ . expect ( "Failed to create context" ) ;
1468+
1469+ let detail = client. variation_detail ( & context, "flag" , FlagValue :: Bool ( false ) ) ;
1470+
1471+ assert ! ( detail. value. unwrap( ) . as_bool( ) . unwrap( ) ) ;
1472+ assert ! ( matches!(
1473+ detail. reason,
1474+ Reason :: Fallthrough {
1475+ in_experiment: false
1476+ }
1477+ ) ) ;
1478+ client. flush ( ) ;
1479+ client. close ( ) ;
1480+
1481+ testing_logger:: validate ( |captured_logs| {
1482+ assert_eq ! ( captured_logs. len( ) , 1 ) ;
1483+ assert_eq ! (
1484+ captured_logs[ 0 ] . body,
1485+ "Started LaunchDarkly Client in daemon mode"
1486+ ) ;
1487+ } ) ;
1488+ }
1489+
1490+ #[ test]
1491+ fn daemon_mode_is_quiet_if_store_is_not_initialized ( ) {
1492+ testing_logger:: setup ( ) ;
1493+
1494+ let factory = InMemoryPersistentDataStoreFactory {
1495+ data : AllData {
1496+ flags : HashMap :: new ( ) ,
1497+ segments : HashMap :: new ( ) ,
1498+ } ,
1499+ initialized : false ,
1500+ } ;
1501+ let builder = PersistentDataStoreBuilder :: new ( Arc :: new ( factory) ) ;
1502+
1503+ let config = ConfigBuilder :: new ( "sdk-key" )
1504+ . daemon_mode ( true )
1505+ . data_store ( & builder)
1506+ . event_processor ( & NullEventProcessorBuilder :: new ( ) )
1507+ . build ( )
1508+ . expect ( "config should build" ) ;
1509+
1510+ let client = Client :: build ( config) . expect ( "Should be built." ) ;
1511+
1512+ client. start_with_default_executor ( ) ;
1513+
1514+ let context = ContextBuilder :: new ( "bob" )
1515+ . build ( )
1516+ . expect ( "Failed to create context" ) ;
1517+
1518+ client. variation_detail ( & context, "flag" , FlagValue :: Bool ( false ) ) ;
1519+
1520+ testing_logger:: validate ( |captured_logs| {
1521+ assert_eq ! ( captured_logs. len( ) , 1 ) ;
1522+ assert_eq ! (
1523+ captured_logs[ 0 ] . body,
1524+ "Started LaunchDarkly Client in daemon mode"
1525+ ) ;
1526+ } ) ;
1527+ }
1528+
13961529 #[ test]
13971530 fn variation_handles_off_flag_without_variation ( ) {
13981531 let ( client, event_rx) = make_mocked_client ( ) ;
@@ -1612,7 +1745,7 @@ mod tests {
16121745
16131746 #[ tokio:: test]
16141747 async fn variation_detail_handles_client_not_ready ( ) {
1615- let ( client, event_rx) = make_mocked_client_with_delay ( u64:: MAX , false ) ;
1748+ let ( client, event_rx) = make_mocked_client_with_delay ( u64:: MAX , false , false ) ;
16161749 client. start_with_default_executor ( ) ;
16171750 let context = ContextBuilder :: new ( "bob" )
16181751 . build ( )
@@ -2475,12 +2608,17 @@ mod tests {
24752608 }
24762609 }
24772610
2478- fn make_mocked_client_with_delay ( delay : u64 , offline : bool ) -> ( Client , Receiver < OutputEvent > ) {
2611+ fn make_mocked_client_with_delay (
2612+ delay : u64 ,
2613+ offline : bool ,
2614+ daemon_mode : bool ,
2615+ ) -> ( Client , Receiver < OutputEvent > ) {
24792616 let updates = Arc :: new ( MockDataSource :: new_with_init_delay ( delay) ) ;
24802617 let ( event_sender, event_rx) = create_event_sender ( ) ;
24812618
24822619 let config = ConfigBuilder :: new ( "sdk-key" )
24832620 . offline ( offline)
2621+ . daemon_mode ( daemon_mode)
24842622 . data_source ( MockDataSourceBuilder :: new ( ) . data_source ( updates) )
24852623 . event_processor (
24862624 EventProcessorBuilder :: < HttpConnector > :: new ( ) . event_sender ( Arc :: new ( event_sender) ) ,
@@ -2494,10 +2632,10 @@ mod tests {
24942632 }
24952633
24962634 fn make_mocked_offline_client ( ) -> ( Client , Receiver < OutputEvent > ) {
2497- make_mocked_client_with_delay ( 0 , true )
2635+ make_mocked_client_with_delay ( 0 , true , false )
24982636 }
24992637
25002638 fn make_mocked_client ( ) -> ( Client , Receiver < OutputEvent > ) {
2501- make_mocked_client_with_delay ( 0 , false )
2639+ make_mocked_client_with_delay ( 0 , false , false )
25022640 }
25032641}
0 commit comments