@@ -31,9 +31,12 @@ mod moved_tests {
3131 ConnHandle , HibernatableConnectionMetadata , decode_persisted_connection,
3232 } ;
3333 use crate :: actor:: context:: tests:: new_with_kv;
34- use crate :: actor:: keys:: { LAST_PUSHED_ALARM_KEY , PERSIST_DATA_KEY , make_connection_key} ;
34+ use crate :: actor:: keys:: {
35+ CONN_PREFIX , INSPECTOR_TOKEN_KEY , LAST_PUSHED_ALARM_KEY , PERSIST_DATA_KEY ,
36+ QUEUE_MESSAGES_PREFIX , WORKFLOW_STORAGE_PREFIX , make_connection_key,
37+ } ;
3538 use crate :: actor:: messages:: { ActorEvent , SerializeStateReason , StateDelta } ;
36- use crate :: actor:: preload:: PreloadedPersistedActor ;
39+ use crate :: actor:: preload:: { PreloadedKv , PreloadedPersistedActor } ;
3740 use crate :: actor:: sleep:: CanSleep ;
3841 use crate :: actor:: state:: {
3942 PersistedActor , PersistedScheduleEvent , RequestSaveOpts , decode_last_pushed_alarm,
@@ -47,6 +50,7 @@ mod moved_tests {
4750 use crate :: inspector:: auth:: test_inspector_env_lock;
4851 use crate :: kv:: tests:: new_in_memory;
4952 use crate :: { ActorConfig , ActorContext , ActorFactory } ;
53+ use rivet_envoy_client:: utils:: EnvoyShutdownError ;
5054
5155 fn test_hook_lock ( ) -> & ' static AsyncMutex < ( ) > {
5256 static LOCK : OnceLock < AsyncMutex < ( ) > > = OnceLock :: new ( ) ;
@@ -2108,6 +2112,183 @@ mod moved_tests {
21082112 assert ! ( ctx. persisted_actor( ) . has_initialized) ;
21092113 }
21102114
2115+ #[ tokio:: test]
2116+ async fn startup_uses_preloaded_last_pushed_alarm_without_live_kv ( ) {
2117+ let _env_guard = test_inspector_env_lock ( ) . lock ( ) . expect ( "env lock poisoned" ) ;
2118+ unsafe {
2119+ std:: env:: remove_var ( "_RIVET_TEST_INSPECTOR_TOKEN" ) ;
2120+ }
2121+
2122+ let future_ts = 4_102_445_000_000 ;
2123+ let persisted = PersistedActor {
2124+ has_initialized : true ,
2125+ scheduled_events : vec ! [ PersistedScheduleEvent {
2126+ event_id: "evt-preloaded-alarm" . to_owned( ) ,
2127+ timestamp: future_ts,
2128+ action: "tick" . to_owned( ) ,
2129+ args: None ,
2130+ } ] ,
2131+ ..PersistedActor :: default ( )
2132+ } ;
2133+ let encoded_alarm =
2134+ encode_last_pushed_alarm ( Some ( future_ts) ) . expect ( "last pushed alarm should encode" ) ;
2135+ let preloaded_kv = PreloadedKv :: new_with_requested_get_keys (
2136+ vec ! [
2137+ ( LAST_PUSHED_ALARM_KEY . to_vec( ) , encoded_alarm) ,
2138+ (
2139+ INSPECTOR_TOKEN_KEY . to_vec( ) ,
2140+ b"startup-preload-token" . to_vec( ) ,
2141+ ) ,
2142+ ] ,
2143+ vec ! [
2144+ PERSIST_DATA_KEY . to_vec( ) ,
2145+ LAST_PUSHED_ALARM_KEY . to_vec( ) ,
2146+ INSPECTOR_TOKEN_KEY . to_vec( ) ,
2147+ vec![ 5 , 1 , 1 ] ,
2148+ ] ,
2149+ vec ! [
2150+ WORKFLOW_STORAGE_PREFIX . to_vec( ) ,
2151+ CONN_PREFIX . to_vec( ) ,
2152+ QUEUE_MESSAGES_PREFIX . to_vec( ) ,
2153+ ] ,
2154+ ) ;
2155+
2156+ let ( handle, mut rx) = test_envoy_handle ( ) ;
2157+ tokio:: spawn ( async move {
2158+ while let Some ( message) = rx. recv ( ) . await {
2159+ if let ToEnvoyMessage :: KvRequest { response_tx, .. } = message {
2160+ let _ = response_tx. send ( Err ( anyhow:: anyhow!( EnvoyShutdownError ) ) ) ;
2161+ }
2162+ }
2163+ } ) ;
2164+
2165+ let ctx = new_with_kv (
2166+ "actor-preloaded-alarm" ,
2167+ "task-preloaded-alarm" ,
2168+ Vec :: new ( ) ,
2169+ "local" ,
2170+ crate :: kv:: Kv :: new ( handle. clone ( ) , "actor-preloaded-alarm" ) ,
2171+ ) ;
2172+ ctx. configure_envoy ( handle, Some ( 31 ) ) ;
2173+ let mut task = new_task ( ctx. clone ( ) )
2174+ . with_preloaded_persisted_actor ( PreloadedPersistedActor :: Some ( persisted) )
2175+ . with_preloaded_kv ( Some ( preloaded_kv) ) ;
2176+ let ( start_tx, start_rx) = oneshot:: channel ( ) ;
2177+
2178+ task. handle_lifecycle ( LifecycleCommand :: Start { reply : start_tx } )
2179+ . await ;
2180+ start_rx
2181+ . await
2182+ . expect ( "start reply should send" )
2183+ . expect ( "start should use preloaded alarm without live kv" ) ;
2184+
2185+ assert_eq ! ( ctx. last_pushed_alarm( ) , Some ( future_ts) ) ;
2186+ }
2187+
2188+ #[ tokio:: test]
2189+ async fn startup_uses_preloaded_alarm_with_partial_startup_preload ( ) {
2190+ let _env_guard = test_inspector_env_lock ( ) . lock ( ) . expect ( "env lock poisoned" ) ;
2191+ unsafe {
2192+ std:: env:: remove_var ( "_RIVET_TEST_INSPECTOR_TOKEN" ) ;
2193+ }
2194+
2195+ let future_ts = 4_102_445_123_000 ;
2196+ let persisted = PersistedActor {
2197+ has_initialized : true ,
2198+ scheduled_events : vec ! [ PersistedScheduleEvent {
2199+ event_id: "evt-partial-preload-alarm" . to_owned( ) ,
2200+ timestamp: future_ts,
2201+ action: "tick" . to_owned( ) ,
2202+ args: None ,
2203+ } ] ,
2204+ ..PersistedActor :: default ( )
2205+ } ;
2206+ let encoded_persisted =
2207+ encode_persisted_actor ( & persisted) . expect ( "persisted actor should encode" ) ;
2208+ let encoded_alarm =
2209+ encode_last_pushed_alarm ( Some ( future_ts) ) . expect ( "last pushed alarm should encode" ) ;
2210+ let preloaded_kv = PreloadedKv :: new_with_requested_get_keys (
2211+ vec ! [
2212+ ( LAST_PUSHED_ALARM_KEY . to_vec( ) , encoded_alarm) ,
2213+ (
2214+ INSPECTOR_TOKEN_KEY . to_vec( ) ,
2215+ b"startup-partial-preload-token" . to_vec( ) ,
2216+ ) ,
2217+ ] ,
2218+ vec ! [
2219+ LAST_PUSHED_ALARM_KEY . to_vec( ) ,
2220+ INSPECTOR_TOKEN_KEY . to_vec( ) ,
2221+ vec![ 5 , 1 , 1 ] ,
2222+ ] ,
2223+ vec ! [
2224+ WORKFLOW_STORAGE_PREFIX . to_vec( ) ,
2225+ CONN_PREFIX . to_vec( ) ,
2226+ QUEUE_MESSAGES_PREFIX . to_vec( ) ,
2227+ ] ,
2228+ ) ;
2229+
2230+ let saw_persist_live_get = Arc :: new ( AtomicBool :: new ( false ) ) ;
2231+ let saw_persist_live_get_for_task = Arc :: clone ( & saw_persist_live_get) ;
2232+ let ( handle, mut rx) = test_envoy_handle ( ) ;
2233+ tokio:: spawn ( async move {
2234+ while let Some ( message) = rx. recv ( ) . await {
2235+ if let ToEnvoyMessage :: KvRequest {
2236+ data, response_tx, ..
2237+ } = message
2238+ {
2239+ match data {
2240+ protocol:: KvRequestData :: KvGetRequest ( request) => {
2241+ assert_eq ! (
2242+ request. keys,
2243+ vec![ PERSIST_DATA_KEY . to_vec( ) ] ,
2244+ "partial preload should only live-fetch missing persisted actor data"
2245+ ) ;
2246+ saw_persist_live_get_for_task. store ( true , Ordering :: SeqCst ) ;
2247+ let _ = response_tx. send ( Ok (
2248+ protocol:: KvResponseData :: KvGetResponse ( protocol:: KvGetResponse {
2249+ keys : vec ! [ PERSIST_DATA_KEY . to_vec( ) ] ,
2250+ values : vec ! [ encoded_persisted. clone( ) ] ,
2251+ metadata : vec ! [ protocol:: KvMetadata {
2252+ version: Vec :: new( ) ,
2253+ update_ts: 0 ,
2254+ } ] ,
2255+ } ) ,
2256+ ) ) ;
2257+ }
2258+ protocol:: KvRequestData :: KvPutRequest ( _) => {
2259+ let _ = response_tx. send ( Ok ( protocol:: KvResponseData :: KvPutResponse ) ) ;
2260+ }
2261+ other => {
2262+ let _ = response_tx
2263+ . send ( Err ( anyhow:: anyhow!( "unexpected KV request: {other:?}" ) ) ) ;
2264+ }
2265+ }
2266+ }
2267+ }
2268+ } ) ;
2269+
2270+ let ctx = new_with_kv (
2271+ "actor-partial-preload-alarm" ,
2272+ "task-partial-preload-alarm" ,
2273+ Vec :: new ( ) ,
2274+ "local" ,
2275+ crate :: kv:: Kv :: new ( handle. clone ( ) , "actor-partial-preload-alarm" ) ,
2276+ ) ;
2277+ ctx. configure_envoy ( handle, Some ( 32 ) ) ;
2278+ let mut task = new_task ( ctx. clone ( ) ) . with_preloaded_kv ( Some ( preloaded_kv) ) ;
2279+ let ( start_tx, start_rx) = oneshot:: channel ( ) ;
2280+
2281+ task. handle_lifecycle ( LifecycleCommand :: Start { reply : start_tx } )
2282+ . await ;
2283+ start_rx
2284+ . await
2285+ . expect ( "start reply should send" )
2286+ . expect ( "start should use preloaded alarm in partial preload bundle" ) ;
2287+
2288+ assert ! ( saw_persist_live_get. load( Ordering :: SeqCst ) ) ;
2289+ assert_eq ! ( ctx. last_pushed_alarm( ) , Some ( future_ts) ) ;
2290+ }
2291+
21112292 #[ tokio:: test]
21122293 async fn startup_skips_future_alarm_push_when_last_pushed_matches ( ) {
21132294 let kv = new_in_memory ( ) ;
0 commit comments