1use std::collections::VecDeque;
30use std::future;
31use std::panic::AssertUnwindSafe;
32use std::sync::Arc;
33use std::sync::atomic::{AtomicU32, Ordering};
34use std::time::Duration;
35
36use anyhow::{Context, Result, anyhow};
37use futures::FutureExt;
38use tokio::sync::{broadcast, mpsc, oneshot, watch};
39use tokio::task::{JoinError, JoinHandle};
40use tokio_util::sync::CancellationToken;
41use tracing::{Instrument, instrument::WithSubscriber};
42
43use crate::actor::action::ActionDispatchError;
44use crate::actor::connection::ConnHandle;
45use crate::actor::context::ActorContext;
46use crate::actor::factory::ActorFactory;
47use crate::actor::lifecycle_hooks::{ActorEvents, ActorStart, Reply};
48use crate::actor::messages::{
49 ActorEvent, ActorHttpResponse, QueueSendResult, Request, SerializeStateReason, StateDelta,
50 WorkflowKvWrite,
51};
52use crate::actor::metrics::startup_phase::StartupPhase;
53use crate::actor::state::{PersistedActor, RequestSaveOpts};
54use crate::actor::task_types::ShutdownKind;
55use crate::actor::work_registry::ActorWorkKind;
56use crate::error::{ActorLifecycle as ActorLifecycleError, ActorRuntime};
57use crate::runtime::{RuntimeBoxFuture, RuntimeSpawner, boxed_runtime_future};
58use crate::telemetry::{ActorInvocation, IncomingInvocationContext};
59use crate::time::{Instant, sleep_until, timeout};
60use crate::types::{SaveStateOpts, format_actor_key};
61use crate::websocket::WebSocket;
62
63pub type ActionDispatchResult = std::result::Result<Vec<u8>, ActionDispatchError>;
64pub type HttpDispatchResult = Result<ActorHttpResponse>;
65
66const SERIALIZE_STATE_SHUTDOWN_SANITY_CAP: Duration = Duration::from_secs(15);
67const INSPECTOR_SERIALIZE_STATE_INTERVAL: Duration = Duration::from_millis(50);
68const INSPECTOR_OVERLAY_CHANNEL_CAPACITY: usize = 32;
69
70pub use crate::actor::task_types::LifecycleState;
71
72pub enum LifecycleCommand {
80 Start {
81 reply: oneshot::Sender<Result<()>>,
82 },
83 Stop {
84 reason: ShutdownKind,
85 reply: oneshot::Sender<Result<()>>,
86 },
87 FireAlarm {
88 reply: oneshot::Sender<Result<()>>,
89 },
90}
91
92impl LifecycleCommand {
93 fn kind(&self) -> &'static str {
94 match self {
95 Self::Start { .. } => "start",
96 Self::Stop { .. } => "stop",
97 Self::FireAlarm { .. } => "fire_alarm",
98 }
99 }
100
101 fn stop_reason(&self) -> Option<&'static str> {
102 match self {
103 Self::Stop { reason, .. } => Some(shutdown_reason_label(*reason)),
104 Self::Start { .. } => None,
105 Self::FireAlarm { .. } => None,
106 }
107 }
108}
109
110pub(crate) fn try_send_lifecycle_command(
111 sender: &mpsc::UnboundedSender<LifecycleCommand>,
112 command: LifecycleCommand,
113) -> Result<()> {
114 sender
115 .send(command)
116 .map_err(|_| ActorLifecycleError::NotReady.build())
117}
118
119pub enum DispatchCommand {
120 Action {
121 name: String,
122 args: Vec<u8>,
123 incoming: IncomingInvocationContext,
124 conn: ConnHandle,
125 reply: oneshot::Sender<Result<Vec<u8>>>,
126 },
127 QueueSend {
128 name: String,
129 body: Vec<u8>,
130 incoming: crate::telemetry::IncomingInvocationContext,
131 conn: ConnHandle,
132 request: Request,
133 wait: bool,
134 timeout_ms: Option<u64>,
135 reply: oneshot::Sender<Result<QueueSendResult>>,
136 },
137 Http {
138 request: Request,
139 reply: oneshot::Sender<HttpDispatchResult>,
140 },
141 OpenWebSocket {
142 conn: ConnHandle,
143 ws: WebSocket,
144 request: Option<Request>,
145 reply: oneshot::Sender<Result<()>>,
146 },
147 WorkflowHistory {
148 reply: oneshot::Sender<Result<Option<Vec<u8>>>>,
149 },
150 WorkflowReplay {
151 entry_id: Option<String>,
152 reply: oneshot::Sender<Result<Option<Vec<u8>>>>,
153 },
154}
155
156impl DispatchCommand {
157 fn kind(&self) -> &'static str {
158 match self {
159 Self::Action { .. } => "action",
160 Self::QueueSend { .. } => "queue_send",
161 Self::Http { .. } => "http",
162 Self::OpenWebSocket { .. } => "open_websocket",
163 Self::WorkflowHistory { .. } => "workflow_history",
164 Self::WorkflowReplay { .. } => "workflow_replay",
165 }
166 }
167}
168
169pub(crate) fn try_send_dispatch_command(
170 sender: &mpsc::UnboundedSender<DispatchCommand>,
171 command: DispatchCommand,
172) -> Result<()> {
173 sender
174 .send(command)
175 .map_err(|_| ActorLifecycleError::NotReady.build())
176}
177
178#[derive(Debug)]
179pub enum LifecycleEvent {
180 SaveRequested {
181 immediate: bool,
182 },
183 WorkflowFlushRequested {
184 writes: Vec<WorkflowKvWrite>,
185 reply: Reply<()>,
186 },
187 InspectorSerializeRequested,
188 InspectorAttachmentsChanged,
189 SleepTick,
190 StateTransactionFinished,
193}
194
195impl LifecycleEvent {
196 fn kind(&self) -> &'static str {
197 match self {
198 Self::SaveRequested { .. } => "save_requested",
199 Self::WorkflowFlushRequested { .. } => "workflow_flush_requested",
200 Self::InspectorSerializeRequested => "inspector_serialize_requested",
201 Self::InspectorAttachmentsChanged => "inspector_attachments_changed",
202 Self::SleepTick => "sleep_tick",
203 Self::StateTransactionFinished => "state_transaction_finished",
204 }
205 }
206}
207
208impl PartialEq for LifecycleEvent {
209 fn eq(&self, other: &Self) -> bool {
210 match self {
211 Self::SaveRequested { immediate: left } => {
212 if let Self::SaveRequested { immediate: right } = other {
213 left == right
214 } else {
215 false
216 }
217 }
218 Self::WorkflowFlushRequested { .. } => false,
219 Self::InspectorSerializeRequested => {
220 matches!(other, Self::InspectorSerializeRequested)
221 }
222 Self::InspectorAttachmentsChanged => {
223 matches!(other, Self::InspectorAttachmentsChanged)
224 }
225 Self::SleepTick => matches!(other, Self::SleepTick),
226 Self::StateTransactionFinished => matches!(other, Self::StateTransactionFinished),
227 }
228 }
229}
230
231impl Eq for LifecycleEvent {}
232
233enum LiveExit {
234 Shutdown { reason: ShutdownKind },
235 Terminated,
236}
237
238struct SleepGraceState {
239 deadline: Instant,
240 reason: ShutdownKind,
241}
242
243struct PersistedStartup {
244 actor: PersistedActor,
245 last_pushed_alarm: Option<i64>,
246 run_wake_at: Option<i64>,
247 inspector_token: Option<String>,
249}
250
251struct PendingLifecycleReply {
252 command: &'static str,
253 reason: Option<&'static str>,
254 reply: oneshot::Sender<Result<()>>,
255}
256
257pub struct ActorTask {
258 pub actor_id: String,
260 pub generation: u32,
261
262 pub lifecycle_inbox: mpsc::UnboundedReceiver<LifecycleCommand>,
266 pub dispatch_inbox: mpsc::UnboundedReceiver<DispatchCommand>,
270 pub lifecycle_events: mpsc::UnboundedReceiver<LifecycleEvent>,
274
275 pub lifecycle: LifecycleState,
277 pub factory: Arc<ActorFactory>,
278 pub ctx: ActorContext,
279
280 pub start_input: Option<Vec<u8>>,
282
283 actor_event_tx: Option<mpsc::UnboundedSender<ActorEvent>>,
287 actor_event_rx: Option<mpsc::UnboundedReceiver<ActorEvent>>,
291 run_handle: Option<JoinHandle<Result<()>>>,
294 pending_persistence: Option<PendingPersistence>,
297 unpersisted_serialization: bool,
299 pending_inspector: Option<RuntimeBoxFuture<Result<Vec<StateDelta>>>>,
302
303 inspector_attach_count: Arc<AtomicU32>,
307 inspector_overlay_tx: broadcast::Sender<Arc<Vec<u8>>>,
310
311 pub state_save_deadline: Option<Instant>,
315 pub inspector_serialize_state_deadline: Option<Instant>,
318 pub sleep_deadline: Option<Instant>,
321
322 shutdown_reply: Option<PendingLifecycleReply>,
326 sleep_grace: Option<SleepGraceState>,
329 deferred_workflow_flushes: VecDeque<(Vec<WorkflowKvWrite>, Reply<()>)>,
333 explicit_transaction_wait: Option<watch::Receiver<bool>>,
337}
338
339struct PendingPersistence {
341 future: RuntimeBoxFuture<Result<bool>>,
342 completion: PersistenceCompletion,
343}
344
345enum PersistenceCompletion {
346 StateSave,
347 Workflow {
348 writes: Vec<WorkflowKvWrite>,
349 reply: Reply<()>,
350 },
351}
352
353impl ActorTask {
354 pub fn new(
355 actor_id: String,
356 generation: u32,
357 lifecycle_inbox: mpsc::UnboundedReceiver<LifecycleCommand>,
358 dispatch_inbox: mpsc::UnboundedReceiver<DispatchCommand>,
359 lifecycle_events: mpsc::UnboundedReceiver<LifecycleEvent>,
360 factory: Arc<ActorFactory>,
361 ctx: ActorContext,
362 start_input: Option<Vec<u8>>,
363 ) -> Self {
364 let (actor_event_tx, actor_event_rx) = mpsc::unbounded_channel();
365 let (inspector_overlay_tx, _) = broadcast::channel(INSPECTOR_OVERLAY_CHANNEL_CAPACITY);
366 let inspector_attach_count = Arc::new(AtomicU32::new(0));
367 ctx.configure_inspector_runtime(
368 Arc::clone(&inspector_attach_count),
369 inspector_overlay_tx.clone(),
370 );
371 let inspector_ctx = ctx.downgrade();
372 let inspector_attach_count_for_hook = Arc::clone(&inspector_attach_count);
373 ctx.on_request_save(Box::new(move |_opts| {
374 if inspector_attach_count_for_hook.load(Ordering::SeqCst) > 0 {
375 if let Some(ctx) = ActorContext::from_weak(&inspector_ctx) {
376 ctx.notify_inspector_serialize_requested();
377 }
378 }
379 }));
380 Self {
381 actor_id,
382 generation,
383 lifecycle_inbox,
384 dispatch_inbox,
385 lifecycle_events,
386 lifecycle: LifecycleState::default(),
387 factory,
388 ctx,
389 start_input,
390 actor_event_tx: Some(actor_event_tx),
391 actor_event_rx: Some(actor_event_rx),
392 run_handle: None,
393 pending_persistence: None,
394 unpersisted_serialization: false,
395 pending_inspector: None,
396 inspector_attach_count,
397 inspector_overlay_tx,
398 state_save_deadline: None,
399 inspector_serialize_state_deadline: None,
400 sleep_deadline: None,
401 shutdown_reply: None,
402 sleep_grace: None,
403 deferred_workflow_flushes: VecDeque::new(),
404 explicit_transaction_wait: None,
405 }
406 }
407
408 #[tracing::instrument(
409 skip_all,
410 fields(
411 actor_id = %self.actor_id,
412 generation = self.generation,
413 actor_key = %format_actor_key(self.ctx.key()),
414 ),
415 )]
416 pub async fn run(mut self) -> Result<()> {
417 let _unpersisted_saves = FailUnpersistedSavesOnDrop(self.ctx.clone());
419 let exit = self.run_live().await;
420 let LiveExit::Shutdown { reason } = exit else {
421 self.ctx.cancel_actor_abort_signal();
424 self.ctx.cancel_shutdown_deadline();
425 self.ctx.mark_shutdown_deadline_reached();
426 if let Some(handle) = self.run_handle.as_mut() {
427 handle.abort();
428 }
429 self.pending_inspector = None;
430 self.finish_pending_persistence().await;
431 self.ctx.fail_unpersisted_save_requests();
432 self.join_aborted_run_handle().await;
433 self.record_inbox_depths();
434 self.ctx.metrics().record_actor_stopped();
435 return Ok(());
436 };
437
438 let result = match AssertUnwindSafe(self.run_shutdown(reason))
439 .catch_unwind()
440 .await
441 {
442 Ok(result) => result,
443 Err(_) => Err(anyhow!("shutdown panicked during {reason:?}")),
444 };
445 self.deliver_shutdown_reply(reason, &result);
446 self.transition_to(LifecycleState::Terminated);
447 self.record_inbox_depths();
448 self.ctx.metrics().record_actor_stopped();
449 result
450 }
451
452 async fn run_live(&mut self) -> LiveExit {
453 let activity_notify = self.ctx.sleep_activity_notify();
454
455 loop {
456 if self.ctx.acknowledge_activity_dirty() {
457 if let Some(exit) = self.on_activity_signal().await {
458 return exit;
459 }
460 }
461 self.record_inbox_depths();
463 tokio::select! {
464 biased;
465 lifecycle_command = self.lifecycle_inbox.recv() => {
466 match lifecycle_command {
467 Some(command) => {
468 if let Some(exit) = self.handle_lifecycle(command).await {
469 return exit;
470 }
471 }
472 None => {
473 self.log_closed_channel(
474 "lifecycle_inbox",
475 "actor task terminating because lifecycle command inbox closed",
476 );
477 return LiveExit::Terminated;
478 }
479 }
480 }
481 lifecycle_event = self.lifecycle_events.recv() => {
482 match lifecycle_event {
483 Some(event) => self.handle_event(event).await,
484 None => {
485 self.log_closed_channel(
486 "lifecycle_events",
487 "actor task terminating because lifecycle event inbox closed",
488 );
489 return LiveExit::Terminated;
490 }
491 }
492 }
493 _ = activity_notify.notified() => {
494 self.ctx.acknowledge_activity_dirty();
495 if let Some(exit) = self.on_activity_signal().await {
496 return exit;
497 }
498 }
499 _ = Self::sleep_grace_tick(self.sleep_grace.as_ref().map(|grace| grace.deadline)), if self.sleep_grace.is_some() => {
500 if let Some(exit) = self.on_sleep_grace_deadline().await {
501 return exit;
502 }
503 }
504 dispatch_command = self.dispatch_inbox.recv(), if self.accepting_dispatch() => {
505 match dispatch_command {
506 Some(command) => self.handle_dispatch(command).await,
507 None => {
508 self.log_closed_channel(
509 "dispatch_inbox",
510 "actor task terminating because dispatch inbox closed",
511 );
512 return LiveExit::Terminated;
513 }
514 }
515 }
516 outcome = Self::wait_for_run_handle(self.run_handle.as_mut()), if self.run_handle.is_some() => {
517 if let Some(exit) = self.handle_run_handle_outcome(outcome) {
518 return exit;
519 }
520 }
521 outcome = Self::wait_for_persistence(self.pending_persistence.as_mut()), if self.pending_persistence.is_some() => {
522 self.complete_persistence(outcome);
523 self.drain_deferred_workflow_flushes().await;
524 if let Some(exit) = self.try_finish_grace() { return exit; }
525 }
526 outcome = Self::wait_for_inspector(self.pending_inspector.as_mut()), if self.pending_inspector.is_some() => {
527 self.pending_inspector = None;
528 self.on_inspector_finished(outcome);
529 }
530 _ = Self::state_save_tick(self.state_save_deadline), if self.state_save_timer_active() => {
531 self.start_state_save();
532 }
533 _ = Self::explicit_transaction_finished(self.explicit_transaction_wait.as_mut()), if self.explicit_transaction_wait.is_some() => {
534 self.on_explicit_transaction_finished();
535 }
536 _ = Self::inspector_serialize_state_tick(self.inspector_serialize_state_deadline), if self.inspector_serialize_timer_active() => {
537 self.start_inspector_serialize_state();
538 }
539 _ = Self::sleep_tick(self.sleep_deadline), if self.sleep_timer_active() => {
540 self.on_sleep_tick().await;
541 }
542 }
543
544 if self.should_terminate() {
545 return LiveExit::Terminated;
546 }
547 }
548 }
549
550 async fn handle_lifecycle(&mut self, command: LifecycleCommand) -> Option<LiveExit> {
551 let command_kind = command.kind();
552 let reason = command.stop_reason();
553 self.log_lifecycle_command_received(command_kind, reason);
554 if matches!(
555 self.lifecycle,
556 LifecycleState::SleepGrace | LifecycleState::DestroyGrace
557 ) {
558 return self
559 .handle_sleep_grace_lifecycle(command, command_kind, reason)
560 .await;
561 }
562 match command {
563 LifecycleCommand::Start { reply } => {
564 let result = self.start_actor().await;
565 let failed = result.is_err();
566 if failed {
567 self.finish_failed_startup().await;
568 }
569 self.reply_lifecycle_command(command_kind, reason, reply, result);
570 failed.then_some(LiveExit::Terminated)
571 }
572 LifecycleCommand::Stop { reason, reply } => {
573 self.begin_stop(
574 reason,
575 command_kind,
576 Some(shutdown_reason_label(reason)),
577 reply,
578 )
579 .await
580 }
581 LifecycleCommand::FireAlarm { reply } => {
582 let result = self.fire_due_alarms().await;
583 self.reply_lifecycle_command(command_kind, reason, reply, result);
584 None
585 }
586 }
587 }
588
589 async fn handle_sleep_grace_lifecycle(
590 &mut self,
591 command: LifecycleCommand,
592 command_kind: &'static str,
593 command_reason: Option<&'static str>,
594 ) -> Option<LiveExit> {
595 match command {
596 LifecycleCommand::Start { reply } => {
597 self.reply_lifecycle_command(
598 command_kind,
599 command_reason,
600 reply,
601 Err(ActorLifecycleError::Stopping.build()),
602 );
603 None
604 }
605 LifecycleCommand::Stop { reason, reply } => {
606 let current_reason = self.sleep_grace.as_ref().map(|grace| grace.reason);
607 if current_reason != Some(reason) {
608 debug_assert!(false, "engine actor2 sends one Stop per actor instance");
609 tracing::warn!(
610 actor_id = %self.ctx.actor_id(),
611 reason = shutdown_reason_label(reason),
612 current_reason = ?current_reason,
613 "conflicting Stop during grace, ignoring"
614 );
615 }
616 self.reply_lifecycle_command(command_kind, command_reason, reply, Ok(()));
617 None
618 }
619 LifecycleCommand::FireAlarm { reply } => {
620 let result = self.fire_due_alarms().await;
621 self.reply_lifecycle_command(command_kind, command_reason, reply, result);
622 None
623 }
624 }
625 }
626
627
628
629 async fn begin_stop(
630 &mut self,
631 reason: ShutdownKind,
632 command: &'static str,
633 command_reason: Option<&'static str>,
634 reply: oneshot::Sender<Result<()>>,
635 ) -> Option<LiveExit> {
636 match self.lifecycle {
637 LifecycleState::Started => {
638 self.register_shutdown_reply(command, command_reason, reply);
639 self.drain_accepted_dispatch().await;
640 self.begin_grace(reason).await;
641 self.try_finish_grace()
642 }
643 LifecycleState::SleepGrace | LifecycleState::DestroyGrace => {
644 let current_reason = self.sleep_grace.as_ref().map(|grace| grace.reason);
645 if current_reason == Some(reason) {
646 self.reply_lifecycle_command(command, command_reason, reply, Ok(()));
647 None
648 } else {
649 debug_assert!(false, "engine actor2 sends one Stop per actor instance");
650 tracing::warn!(
651 actor_id = %self.ctx.actor_id(),
652 reason = shutdown_reason_label(reason),
653 current_reason = ?current_reason,
654 "conflicting Stop during grace, ignoring"
655 );
656 self.reply_lifecycle_command(command, command_reason, reply, Ok(()));
657 None
658 }
659 }
660 LifecycleState::SleepFinalize | LifecycleState::Destroying => {
661 debug_assert!(false, "engine actor2 sends one Stop per actor instance");
662 tracing::warn!(
663 actor_id = %self.ctx.actor_id(),
664 reason = shutdown_reason_label(reason),
665 "duplicate Stop after shutdown started, ignoring"
666 );
667 self.reply_lifecycle_command(command, command_reason, reply, Ok(()));
668 None
669 }
670 LifecycleState::Terminated => {
671 self.reply_lifecycle_command(command, command_reason, reply, Ok(()));
672 None
673 }
674 LifecycleState::Loading => {
675 self.reply_lifecycle_command(
676 command,
677 command_reason,
678 reply,
679 Err(ActorLifecycleError::NotReady.build()),
680 );
681 None
682 }
683 }
684 }
685
686 async fn drain_accepted_dispatch(&mut self) {
687 while self.accepting_dispatch() {
688 let Ok(command) = self.dispatch_inbox.try_recv() else {
689 break;
690 };
691 self.handle_dispatch(command).await;
692 }
693 }
694
695 async fn begin_grace(&mut self, reason: ShutdownKind) {
696 tracing::debug!(
697 actor_id = %self.ctx.actor_id(),
698 reason = shutdown_reason_label(reason),
699 "actor grace shutdown started"
700 );
701 self.ctx.sql().mark_background();
704 self.ctx.suspend_alarm_dispatch();
705 self.ctx.cancel_local_alarm_timeouts();
706 self.ctx.set_local_alarm_callback(None);
707 self.transition_to(match reason {
708 ShutdownKind::Sleep => LifecycleState::SleepGrace,
709 ShutdownKind::Destroy => LifecycleState::DestroyGrace,
710 });
711 self.start_grace(reason);
712 self.emit_grace_events(reason);
713 }
714
715 fn emit_grace_events(&mut self, reason: ShutdownKind) {
716 let conns: Vec<_> = self.ctx.conns().collect();
717 for conn in conns {
718 let hibernatable_sleep =
719 matches!(reason, ShutdownKind::Sleep) && conn.is_hibernatable();
720 if hibernatable_sleep {
721 self.ctx.request_hibernation_transport_save(conn.id());
722 continue;
723 }
724 self.ctx.begin_core_dispatched_hook();
725 let reply = self.core_dispatched_hook_reply("disconnect_conn");
726 let conn_id = conn.id().to_owned();
727 if let Err(error) = self.send_actor_event(
728 "grace_disconnect_conn",
729 ActorEvent::DisconnectConn { conn_id, reply },
730 ) {
731 tracing::error!(?error, "failed to enqueue disconnect cleanup event");
732 }
733 }
734
735 self.ctx.begin_core_dispatched_hook();
736 let reply = self.core_dispatched_hook_reply("run_graceful_cleanup");
737 if let Err(error) = self.send_actor_event(
738 "grace_run_cleanup",
739 ActorEvent::RunGracefulCleanup { reason, reply },
740 ) {
741 tracing::error!(?error, "failed to enqueue run cleanup event");
742 }
743 self.ctx.reset_sleep_timer();
744 }
745
746 fn core_dispatched_hook_reply(&self, operation: &'static str) -> Reply<()> {
747 let (tx, rx) = oneshot::channel();
748 let ctx = self.ctx.clone();
749 let task = async move {
750 match rx.await {
751 Ok(Ok(())) => {}
752 Ok(Err(error)) => {
753 tracing::error!(?error, operation, "core dispatched hook failed");
754 }
755 Err(error) => {
756 tracing::debug!(?error, operation, "core dispatched hook reply dropped");
757 }
758 }
759 ctx.mark_core_dispatched_hook_completed();
760 }
761 .in_current_span();
762 RuntimeSpawner::spawn(task);
763 tx.into()
764 }
765
766 async fn handle_event(&mut self, event: LifecycleEvent) {
767 tracing::debug!(
768 actor_id = %self.ctx.actor_id(),
769 event = event.kind(),
770 "actor lifecycle event drained"
771 );
772 match event {
773 LifecycleEvent::SaveRequested { immediate } => {
774 self.schedule_state_save(immediate);
775 self.sync_inspector_serialize_deadline();
776 }
777 LifecycleEvent::WorkflowFlushRequested { writes, reply } => {
778 self.deferred_workflow_flushes.push_back((writes, reply));
779 self.drain_deferred_workflow_flushes().await;
780 }
781 LifecycleEvent::StateTransactionFinished => {
782 if self.ctx.save_requested() {
783 self.schedule_state_save(self.ctx.save_requested_immediate());
784 self.sync_inspector_serialize_deadline();
785 }
786 self.drain_deferred_workflow_flushes().await;
787 }
788 LifecycleEvent::InspectorSerializeRequested
789 | LifecycleEvent::InspectorAttachmentsChanged => {
790 self.sync_inspector_serialize_deadline();
791 }
792 LifecycleEvent::SleepTick => {
793 self.on_sleep_tick().await;
794 }
795 }
796 }
797
798 async fn drain_deferred_workflow_flushes(&mut self) {
801 if self.pending_persistence.is_some() {
802 return;
803 }
804 if !matches!(
805 self.lifecycle,
806 LifecycleState::Started | LifecycleState::SleepGrace
807 ) {
808 for (_, reply) in self.deferred_workflow_flushes.drain(..) {
809 reply.send(Err(ActorLifecycleError::NotReady.build()));
810 }
811 return;
812 }
813 if self.ctx.state_transaction_active() {
814 return;
815 }
816 let Some((writes, reply)) = self.deferred_workflow_flushes.pop_front() else {
817 return;
818 };
819 let save_request_revision = self.ctx.save_request_revision();
820 let state_transaction_epoch = self.ctx.state_transaction_epoch();
821 let (reply_tx, reply_rx) = oneshot::channel();
822 if let Err(error) = self.send_actor_event(
823 "workflow_flush_serialize_state",
824 ActorEvent::SerializeState {
825 reason: SerializeStateReason::Save,
826 reply: Reply::from(reply_tx),
827 },
828 ) {
829 reply.send(Err(error));
830 for (_, reply) in self.deferred_workflow_flushes.drain(..) {
832 reply.send(Err(ActorLifecycleError::NotReady.build()));
833 }
834 return;
835 }
836 let ctx = self.ctx.clone();
837 let pending_writes = writes.clone();
838 let serialize_cap = self.shutdown_serialize_cap();
839 self.pending_persistence = Some(PendingPersistence {
840 completion: PersistenceCompletion::Workflow { writes, reply },
841 future: boxed_runtime_future(async move {
842 let deltas = await_save_serialization(
843 ctx.shutdown_deadline_token(),
844 reply_rx,
845 serialize_cap,
846 "receive workflow flush serialize-state reply",
847 )
848 .await?;
849 ctx.save_state_and_workflow_batch_at_transaction_epoch(
850 deltas,
851 pending_writes,
852 save_request_revision,
853 state_transaction_epoch,
854 )
855 .await
856 }),
857 });
858 }
859
860 fn complete_persistence(&mut self, outcome: Result<bool>) {
862 let Some(pending) = self.pending_persistence.take() else {
863 tracing::error!(
864 actor_id = %self.ctx.actor_id(),
865 "persistence finished without a pending operation"
866 );
867 return;
868 };
869 self.on_persistence_finished(pending.completion, outcome);
870 }
871
872 fn on_persistence_finished(
873 &mut self,
874 completion: PersistenceCompletion,
875 outcome: Result<bool>,
876 ) {
877 match completion {
878 PersistenceCompletion::StateSave => self.on_state_save_finished(outcome),
879 PersistenceCompletion::Workflow { writes, reply } => match outcome {
880 Ok(true) => reply.send(Ok(())),
881 Ok(false) => {
882 self.ctx.request_save(RequestSaveOpts {
884 immediate: true,
885 max_wait_ms: None,
886 });
887 self.deferred_workflow_flushes.push_front((writes, reply));
888 }
889 Err(error) => {
890 reply.send(Err(error));
891 self.request_resave_after_failed_persistence();
892 }
893 },
894 }
895 }
896
897 async fn handle_dispatch(&mut self, command: DispatchCommand) {
898 let command_kind = command.kind();
899 tracing::debug!(
900 actor_id = %self.ctx.actor_id(),
901 command = command_kind,
902 "actor dispatch command received"
903 );
904 if let Some(error) = self.dispatch_lifecycle_error() {
905 self.reply_dispatch_error(command, error);
906 self.log_dispatch_command_handled(command_kind, "rejected_lifecycle");
907 return;
908 }
909
910 match command {
911 DispatchCommand::Action {
912 name,
913 args,
914 incoming,
915 conn,
916 reply,
917 } => {
918 let invocation = ActorInvocation::start_action(&self.ctx, &name, incoming);
919 let invocation_telemetry = invocation.telemetry();
920 tracing::info!(
921 actor_id = %self.ctx.actor_id(),
922 action_name = %name,
923 conn_id = ?conn.id(),
924 args_len = args.len(),
925 "actor task: handling DispatchCommand::Action"
926 );
927 let (tracked_reply_tx, tracked_reply_rx) = oneshot::channel();
928 let action_name_for_log = name.clone();
929 match self.send_actor_event(
930 "dispatch_action",
931 ActorEvent::Action {
932 name,
933 args,
934 conn: Some(conn),
935 scheduled_fire: None,
936 invocation_telemetry: Some(invocation_telemetry),
937 reply: Reply::from(tracked_reply_tx),
938 },
939 ) {
940 Ok(()) => {
941 tracing::info!(
942 actor_id = %self.ctx.actor_id(),
943 action_name = %action_name_for_log,
944 "actor task: ActorEvent::Action enqueued"
945 );
946 self.log_dispatch_command_handled(command_kind, "enqueued");
947 let actor_id = self.ctx.actor_id().to_owned();
948 let ctx = self.ctx.clone();
949 self.ctx.spawn_work(ActorWorkKind::Action, async move {
950 let result = match tracked_reply_rx.await {
951 Ok(result) => {
952 let result =
953 result.map_err(|error| ctx.attach_actor_to_error(error));
954 tracing::info!(
955 actor_id = %actor_id,
956 action_name = %action_name_for_log,
957 ok = result.is_ok(),
958 "actor task: tracked reply received, forwarding"
959 );
960 result
961 }
962 Err(_) => {
963 tracing::warn!(
964 actor_id = %actor_id,
965 action_name = %action_name_for_log,
966 "actor task: tracked reply dropped before completion"
967 );
968 let error = ctx.attach_actor_to_error(
969 ActorLifecycleError::DroppedReply.build(),
970 );
971 Err(error)
972 }
973 };
974 invocation.finish(result.as_ref().err());
975 let _ = reply.send(result);
976 });
977 }
978 Err(error) => {
979 tracing::warn!(
980 actor_id = %self.ctx.actor_id(),
981 action_name = %action_name_for_log,
982 ?error,
983 "actor task: failed to enqueue ActorEvent::Action"
984 );
985 let error = self.attach_actor_to_error(error);
986 invocation.finish(Some(&error));
987 let _ = reply.send(Err(error));
988 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
989 }
990 }
991 }
992 DispatchCommand::QueueSend {
993 name,
994 body,
995 incoming,
996 conn,
997 request,
998 wait,
999 timeout_ms,
1000 reply,
1001 } => {
1002 let invocation = ActorInvocation::start_queue_send(&self.ctx, &name, incoming);
1003 match self.send_actor_event(
1004 "dispatch_queue_send",
1005 ActorEvent::QueueSend {
1006 name,
1007 body,
1008 conn,
1009 request,
1010 wait,
1011 timeout_ms,
1012 invocation_telemetry: Some(invocation.telemetry()),
1013 reply: Reply::from(reply)
1014 .on_reply(move |result| invocation.finish(result.as_ref().err())),
1015 },
1016 ) {
1017 Ok(()) => {
1018 self.log_dispatch_command_handled(command_kind, "enqueued");
1019 }
1020 Err(_error) => {
1021 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
1022 }
1023 }
1024 }
1025 DispatchCommand::Http { request, reply } => {
1026 let incoming = IncomingInvocationContext::from_http_headers(request.headers());
1027 let invocation = ActorInvocation::start_request(&self.ctx, &request, incoming);
1028 match self.send_actor_event(
1029 "dispatch_http",
1030 ActorEvent::HttpRequest {
1031 request,
1032 invocation_telemetry: Some(invocation.telemetry()),
1033 reply: Reply::from(reply)
1034 .on_reply(move |result| invocation.finish_request(result.as_ref())),
1035 },
1036 ) {
1037 Ok(()) => {
1038 self.log_dispatch_command_handled(command_kind, "enqueued");
1039 }
1040 Err(_error) => {
1041 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
1042 }
1043 }
1044 }
1045 DispatchCommand::OpenWebSocket {
1046 conn,
1047 ws,
1048 request,
1049 reply,
1050 } => {
1051 match self.send_actor_event(
1052 "dispatch_websocket_open",
1053 ActorEvent::WebSocketOpen {
1054 conn,
1055 ws,
1056 request,
1057 reply: Reply::from(reply),
1058 },
1059 ) {
1060 Ok(()) => {
1061 self.log_dispatch_command_handled(command_kind, "enqueued");
1062 }
1063 Err(_error) => {
1064 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
1065 }
1066 }
1067 }
1068 DispatchCommand::WorkflowHistory { reply } => {
1069 match self.send_actor_event(
1070 "dispatch_workflow_history",
1071 ActorEvent::WorkflowHistoryRequested {
1072 reply: Reply::from(reply),
1073 },
1074 ) {
1075 Ok(()) => {
1076 self.log_dispatch_command_handled(command_kind, "enqueued");
1077 }
1078 Err(_error) => {
1079 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
1080 }
1081 }
1082 }
1083 DispatchCommand::WorkflowReplay { entry_id, reply } => {
1084 match self.send_actor_event(
1085 "dispatch_workflow_replay",
1086 ActorEvent::WorkflowReplayRequested {
1087 entry_id,
1088 reply: Reply::from(reply),
1089 },
1090 ) {
1091 Ok(()) => {
1092 self.log_dispatch_command_handled(command_kind, "enqueued");
1093 }
1094 Err(_error) => {
1095 self.log_dispatch_command_handled(command_kind, "enqueue_failed");
1096 }
1097 }
1098 }
1099 }
1100 }
1101
1102 fn log_dispatch_command_handled(&self, command: &'static str, outcome: &'static str) {
1103 tracing::debug!(
1104 actor_id = %self.ctx.actor_id(),
1105 command,
1106 outcome,
1107 "actor dispatch command handled"
1108 );
1109 }
1110
1111 fn send_actor_event(&self, operation: &'static str, event: ActorEvent) -> Result<()> {
1112 let sender = self
1113 .actor_event_tx
1114 .as_ref()
1115 .ok_or_else(|| ActorLifecycleError::NotReady.build())?;
1116 tracing::debug!(
1117 actor_id = %self.ctx.actor_id(),
1118 operation,
1119 event = event.kind(),
1120 "actor event enqueued"
1121 );
1122 sender
1123 .send(event)
1124 .map_err(|_| ActorLifecycleError::NotReady.build())
1125 }
1126
1127 fn reply_dispatch_error(&self, command: DispatchCommand, error: anyhow::Error) {
1128 let error = self.ctx.attach_actor_to_error(error);
1129 match command {
1130 DispatchCommand::Action { reply, .. } => {
1131 let _ = reply.send(Err(error));
1132 }
1133 DispatchCommand::QueueSend { reply, .. } => {
1134 let _ = reply.send(Err(error));
1135 }
1136 DispatchCommand::Http { reply, .. } => {
1137 let _ = reply.send(Err(error));
1138 }
1139 DispatchCommand::OpenWebSocket { reply, .. } => {
1140 let _ = reply.send(Err(error));
1141 }
1142 DispatchCommand::WorkflowHistory { reply } => {
1143 let _ = reply.send(Err(error));
1144 }
1145 DispatchCommand::WorkflowReplay { reply, .. } => {
1146 let _ = reply.send(Err(error));
1147 }
1148 }
1149 }
1150
1151 fn attach_actor_to_error(&self, error: anyhow::Error) -> anyhow::Error {
1152 self.ctx.attach_actor_to_error(error)
1153 }
1154
1155 fn dispatch_lifecycle_error(&self) -> Option<anyhow::Error> {
1156 if self.ctx.destroy_requested() {
1158 self.ctx.warn_work_sent_to_stopping_instance("dispatch");
1159 return Some(ActorLifecycleError::Destroying.build());
1160 }
1161
1162 match self.lifecycle {
1163 LifecycleState::Started | LifecycleState::SleepGrace => None,
1164 LifecycleState::SleepFinalize | LifecycleState::DestroyGrace => {
1165 self.ctx.warn_work_sent_to_stopping_instance("dispatch");
1166 Some(ActorLifecycleError::Stopping.build())
1167 }
1168 LifecycleState::Destroying | LifecycleState::Terminated => {
1169 self.ctx.warn_work_sent_to_stopping_instance("dispatch");
1170 Some(ActorLifecycleError::Destroying.build())
1171 }
1172 LifecycleState::Loading => {
1173 self.ctx.warn_self_call_risk("dispatch");
1174 Some(ActorLifecycleError::NotReady.build())
1175 }
1176 }
1177 }
1178
1179 async fn start_actor(&mut self) -> Result<()> {
1180 let mut startup_timer = self.ctx.metrics().begin_startup_timer();
1181 let actor_id = self.ctx.actor_id().to_owned();
1182 if !self.ctx.started() {
1183 self.ctx.configure_sleep(self.factory.config().clone());
1184 self.ctx
1185 .configure_connection_runtime(self.factory.config().clone());
1186 }
1187 self.ensure_actor_event_channel();
1188 self.ctx.configure_actor_events(self.actor_event_tx.clone());
1189
1190 self.ctx.begin_startup_commits();
1191 let schema_started_at = Instant::now();
1192 let before_storage = self
1194 .ctx
1195 .take_start_received_at()
1196 .map(|received_at| schema_started_at.saturating_duration_since(received_at));
1197 let storage_result = crate::actor::migrate_kv_to_sqlite::ensure_startup_storage(&self.ctx)
1198 .await
1199 .context("initialize internal sqlite schema");
1200 let storage_elapsed = schema_started_at.elapsed();
1201 let mut storage = match storage_result {
1204 Ok(storage) => storage,
1205 Err(error) => {
1206 self.ctx.metrics().observe_startup_phase(
1207 StartupPhase::StartupStorage,
1208 None,
1209 "error",
1210 storage_elapsed,
1211 );
1212 return Err(error);
1213 }
1214 };
1215 let inspector_token_initialized = storage.inspector_token_initialized;
1216 let bootstrapped = storage.bootstrapped;
1217 let first_generation = storage.first_generation;
1218 tracing::debug!(
1219 actor_id = %actor_id,
1220 duration_ms = duration_ms_f64(schema_started_at.elapsed()),
1221 "perf internal: initInternalSqliteSchemaMs"
1222 );
1223
1224 let load_state_started_at = Instant::now();
1225 let load_state_result = self.load_persisted_startup(bootstrapped).await;
1226 let load_state_elapsed = load_state_started_at.elapsed();
1227 let persisted = match load_state_result {
1228 Ok(persisted) => persisted,
1229 Err(error) => {
1230 self.ctx.metrics().observe_startup_phase(
1231 StartupPhase::LoadPersisted,
1232 None,
1233 "error",
1234 load_state_elapsed,
1235 );
1236 return Err(error);
1237 }
1238 };
1239 let is_new = !persisted.actor.has_initialized;
1240 let stored_inspector_token = persisted.inspector_token.clone();
1241 startup_timer.set_is_new(is_new);
1242 self.ctx.metrics().observe_startup_storage(
1243 is_new,
1244 storage_elapsed,
1245 &storage.phases,
1246 load_state_elapsed,
1247 );
1248 if let Some(before_storage) = before_storage {
1249 self.ctx.metrics().observe_startup_phase(
1250 StartupPhase::BeforeStorage,
1251 Some(is_new),
1252 "success",
1253 before_storage,
1254 );
1255 }
1256 tracing::debug!(
1257 actor_id = %actor_id,
1258 duration_ms = duration_ms_f64(load_state_started_at.elapsed()),
1259 "perf internal: loadStateMs"
1260 );
1261
1262 let startup_corked = if is_new {
1267 self.ctx
1268 .sql()
1269 .cork_commits()
1270 .await
1271 .context("hold new actor startup commits")?
1272 } else {
1273 false
1274 };
1275 let inspector_mirror_token = storage.inspector_mirror_token.take();
1276
1277 self.ctx.metrics().set_startup_phase(StartupPhase::CoreInit);
1278 let core_init_started_at = Instant::now();
1279 let core_init_result: Result<()> = async {
1280 self.ctx.load_persisted_actor(persisted.actor);
1281 self.ctx.load_last_pushed_alarm(persisted.last_pushed_alarm);
1282 self.ctx.load_run_wake_at(persisted.run_wake_at);
1283 if !is_new || !self.factory.requires_manual_startup_ready() {
1287 self.ctx.set_has_initialized(true);
1288 self.ctx
1289 .persist_state(SaveStateOpts { immediate: true })
1290 .await
1291 .context("persist actor initialization")?;
1292 }
1293 let init_inspector_token_started_at = Instant::now();
1294 match (&stored_inspector_token, inspector_token_initialized) {
1297 (_, true) => {}
1298 (Some(token), false) => spawn_inspector_mirror_check(&self.ctx, token.clone()),
1299 (None, false) => crate::inspector::auth::init_inspector_token(&self.ctx)
1300 .await
1301 .context("initialize inspector token")?,
1302 }
1303 tracing::debug!(
1304 actor_id = %actor_id,
1305 duration_ms = duration_ms_f64(init_inspector_token_started_at.elapsed()),
1306 "perf internal: initInspectorTokenMs"
1307 );
1308 if !bootstrapped {
1311 self.ctx
1312 .restore_hibernatable_connections()
1313 .await
1314 .context("restore hibernatable connections")?;
1315 }
1316 Self::settle_hibernated_connections(self.ctx.clone())
1317 .await
1318 .context("settle hibernated connections")?;
1319 if bootstrapped {
1320 self.ctx
1321 .init_alarms_for_bootstrapped_actor(first_generation);
1322 } else {
1323 self.ctx.init_alarms().await;
1324 }
1325 Ok(())
1326 }
1327 .await;
1328 self.ctx.metrics().observe_startup_phase_result(
1329 StartupPhase::CoreInit,
1330 Some(is_new),
1331 core_init_started_at,
1332 core_init_result,
1333 )?;
1334
1335 self.transition_to(LifecycleState::Started);
1336 self.ctx
1337 .metrics()
1338 .set_startup_phase(StartupPhase::RuntimePreamble);
1339 let runtime_preamble_started_at = Instant::now();
1340 let runtime_preamble_result = self.spawn_run_handle(is_new).await;
1341 self.ctx.metrics().observe_startup_phase_result(
1342 StartupPhase::RuntimePreamble,
1343 Some(is_new),
1344 runtime_preamble_started_at,
1345 runtime_preamble_result,
1346 )?;
1347
1348 self.ctx
1349 .metrics()
1350 .set_startup_phase(StartupPhase::PostReady);
1351 let post_ready_started_at = Instant::now();
1352 let post_ready_result: Result<()> = async {
1353 if is_new {
1354 if !self.ctx.persisted_actor().has_initialized {
1358 self.ctx.set_has_initialized(true);
1359 }
1360 self.ctx
1361 .persist_state(SaveStateOpts { immediate: true })
1362 .await
1363 .context("persist actor startup state")?;
1364 }
1365 if startup_corked {
1366 self.spawn_startup_commit(inspector_mirror_token);
1367 } else if let Some(token) = inspector_mirror_token {
1368 spawn_inspector_mirror(&self.ctx, token);
1369 }
1370 self.reset_sleep_deadline().await;
1371 if !bootstrapped {
1375 self.ctx.drain_overdue_scheduled_events().await?;
1376 }
1377 self.ctx.finish_startup_commits()?;
1379 Ok(())
1380 }
1381 .await;
1382 self.ctx.metrics().observe_startup_phase_result(
1383 StartupPhase::PostReady,
1384 Some(is_new),
1385 post_ready_started_at,
1386 post_ready_result,
1387 )?;
1388 let startup_elapsed = startup_timer.finish_success();
1389 tracing::debug!(
1390 actor_id = %actor_id,
1391 duration_ms = duration_ms_f64(startup_elapsed),
1392 is_new,
1393 "perf internal: startupTotalMs"
1394 );
1395 Ok(())
1396 }
1397
1398 fn spawn_startup_commit(&self, inspector_mirror_token: Option<String>) {
1402 let ctx = self.ctx.clone();
1403 self.ctx.run_startup_commit(async move {
1404 let started_at = Instant::now();
1405 let vfs_before = ctx.sql().metrics();
1406 let result = ctx
1407 .sql()
1408 .uncork_commits()
1409 .await
1410 .context("commit new actor startup writes");
1411 let outcome = match &result {
1412 Ok(()) => "success",
1413 Err(_) => "error",
1414 };
1415 ctx.metrics().observe_startup_phase(
1416 StartupPhase::StartupCommit,
1417 Some(true),
1418 outcome,
1419 started_at.elapsed(),
1420 );
1421 result?;
1422 if let (Some(before), Some(after)) = (vfs_before, ctx.sql().metrics())
1425 && after.commit_count == before.commit_count + 1
1426 {
1427 let transport_ns = after.transport_ns.saturating_sub(before.transport_ns);
1428 ctx.metrics().observe_startup_phase(
1429 StartupPhase::StartupCommitTransport,
1430 Some(true),
1431 "success",
1432 Duration::from_nanos(transport_ns),
1433 );
1434 }
1435 if let Some(token) = inspector_mirror_token {
1436 spawn_inspector_mirror(&ctx, token);
1437 }
1438 Ok(())
1439 });
1440 }
1441
1442 async fn load_persisted_startup(&mut self, bootstrapped: bool) -> Result<PersistedStartup> {
1445 if bootstrapped {
1446 return Ok(self.fresh_startup());
1447 }
1448 let mut read = crate::actor::internal_storage::load_actor_startup(self.ctx.sql())
1451 .await
1452 .context("load persisted actor startup data from sqlite")?;
1453 if read.import_state.as_deref()
1454 != Some(crate::actor::migrate_kv_to_sqlite::KV_IMPORT_STATE_DONE)
1455 {
1456 crate::actor::migrate_kv_to_sqlite::import_core_state_if_needed(&self.ctx)
1457 .await
1458 .context("import legacy core actor storage to sqlite")?;
1459 read = crate::actor::internal_storage::load_actor_startup(self.ctx.sql())
1460 .await
1461 .context("load persisted actor startup data from sqlite")?;
1462 }
1463 if let Some(snapshot) = read.snapshot {
1464 return Ok(PersistedStartup {
1465 actor: snapshot.actor,
1466 last_pushed_alarm: snapshot.last_pushed_alarm,
1467 run_wake_at: snapshot.run_wake_at,
1468 inspector_token: read.inspector_token,
1469 });
1470 }
1471 let mut fresh = self.fresh_startup();
1472 fresh.inspector_token = read.inspector_token;
1473 Ok(fresh)
1474 }
1475
1476 fn fresh_startup(&self) -> PersistedStartup {
1478 PersistedStartup {
1479 actor: PersistedActor {
1480 input: self.start_input.clone().map(Into::into),
1481 ..PersistedActor::default()
1482 },
1483 last_pushed_alarm: None,
1484 run_wake_at: None,
1485 inspector_token: None,
1486 }
1487 }
1488
1489 fn ensure_actor_event_channel(&mut self) {
1490 if self.actor_event_tx.is_some() && self.actor_event_rx.is_some() {
1491 return;
1492 }
1493
1494 let (actor_event_tx, actor_event_rx) = mpsc::unbounded_channel();
1495 self.actor_event_tx = Some(actor_event_tx);
1496 self.actor_event_rx = Some(actor_event_rx);
1497 }
1498
1499 async fn spawn_run_handle(&mut self, is_new: bool) -> Result<()> {
1500 if self.run_handle.is_some() {
1501 return Ok(());
1502 }
1503
1504 let Some(actor_events) = self.actor_event_rx.take() else {
1505 return Ok(());
1506 };
1507 let requires_manual_startup_ready = self.factory.requires_manual_startup_ready();
1508 let (startup_ready_tx, startup_ready_rx) = if requires_manual_startup_ready {
1509 let (tx, rx) = oneshot::channel();
1510 (Some(tx), Some(rx))
1511 } else {
1512 (None, None)
1513 };
1514 let start = ActorStart {
1515 ctx: self.ctx.clone(),
1516 input: self.ctx.persisted_actor().input.clone().map(Into::into),
1517 is_new,
1518 snapshot: (!is_new).then(|| self.ctx.state()),
1519 hibernated: self
1520 .ctx
1521 .conns()
1522 .filter(|conn| conn.is_hibernatable())
1523 .map(|conn| {
1524 let bytes = conn.state();
1525 (conn, bytes)
1526 })
1527 .collect(),
1528 events: ActorEvents::new(self.ctx.actor_id().to_owned(), actor_events),
1529 startup_ready: startup_ready_tx,
1530 };
1531 let factory = self.factory.clone();
1532 let run_dispatch = tracing::dispatcher::get_default(Clone::clone);
1533 self.run_handle = Some(RuntimeSpawner::spawn(
1534 async move {
1535 match AssertUnwindSafe(factory.start(start)).catch_unwind().await {
1536 Ok(result) => result,
1537 Err(_) => Err(ActorRuntime::Panicked {
1538 operation: "run handler".to_owned(),
1539 }
1540 .build()),
1541 }
1542 }
1543 .in_current_span()
1544 .with_subscriber(run_dispatch),
1545 ));
1546 if let Some(startup_ready_rx) = startup_ready_rx {
1547 startup_ready_rx
1548 .await
1549 .context("receive runtime startup ready reply")?
1550 .context("runtime startup preamble")?;
1551 }
1552 Ok(())
1553 }
1554
1555 async fn settle_hibernated_connections(ctx: ActorContext) -> Result<()> {
1556 let actor_id = ctx.actor_id().to_owned();
1557 let mut dead_conn_ids = Vec::new();
1558 for conn in ctx.conns().filter(|conn| conn.is_hibernatable()) {
1559 let hibernation = conn.hibernation();
1560 let Some(hibernation) = hibernation else {
1561 tracing::debug!(
1562 actor_id = %actor_id,
1563 conn_id = conn.id(),
1564 outcome = "dead_missing_hibernation_metadata",
1565 "hibernated connection settled"
1566 );
1567 dead_conn_ids.push(conn.id().to_owned());
1568 continue;
1569 };
1570 let is_live = ctx
1571 .hibernated_connection_is_live(&hibernation.gateway_id, &hibernation.request_id)?;
1572 if is_live {
1573 tracing::debug!(
1574 actor_id = %actor_id,
1575 conn_id = conn.id(),
1576 outcome = "live",
1577 "hibernated connection settled"
1578 );
1579 continue;
1580 }
1581 tracing::debug!(
1582 actor_id = %actor_id,
1583 conn_id = conn.id(),
1584 outcome = "dead_not_live",
1585 "hibernated connection settled"
1586 );
1587 dead_conn_ids.push(conn.id().to_owned());
1588 }
1589
1590 for conn_id in dead_conn_ids {
1591 ctx.request_hibernation_transport_removal(conn_id.clone());
1592 ctx.remove_conn(&conn_id);
1593 tracing::debug!(
1594 actor_id = %actor_id,
1595 conn_id = %conn_id,
1596 "dead hibernated connection removed"
1597 );
1598 }
1599
1600 Ok(())
1601 }
1602
1603 async fn fire_due_alarms(&mut self) -> Result<()> {
1604 if !matches!(
1605 self.lifecycle,
1606 LifecycleState::Started | LifecycleState::SleepGrace | LifecycleState::DestroyGrace
1607 ) {
1608 return Ok(());
1609 }
1610
1611 let due_run_wake = self.ctx.consume_due_run_wake().await?;
1612 if let Err(error) = self.ctx.drain_overdue_scheduled_events().await {
1613 if self.lifecycle != LifecycleState::DestroyGrace
1614 && let Some((wake_at, wake_revision)) = due_run_wake
1615 && let Err(restore_error) = self
1616 .ctx
1617 .restore_run_wake_at_if_unchanged(wake_at, wake_revision)
1618 .await
1619 {
1620 tracing::error!(
1621 ?restore_error,
1622 wake_at,
1623 "failed to restore run wake after schedule alarm dispatch failed",
1624 );
1625 }
1626 return Err(error);
1627 }
1628 if self.lifecycle == LifecycleState::DestroyGrace {
1632 return Ok(());
1633 }
1634 if let Some((wake_at, wake_revision)) = due_run_wake {
1635 let (reply_tx, reply_rx) = oneshot::channel();
1636 if let Err(error) = self.ctx.try_send_actor_event(
1637 ActorEvent::RunWake {
1638 wake_at,
1639 wake_revision,
1640 reply: Reply::from(reply_tx),
1641 },
1642 "run_wake",
1643 ) {
1644 self.ctx
1645 .restore_run_wake_at_if_unchanged(wake_at, wake_revision)
1646 .await?;
1647 return Err(error).context("dispatch due run wake");
1648 }
1649 let restart_result = match reply_rx.await {
1650 Ok(result) => result,
1651 Err(error) => {
1652 self.ctx
1653 .restore_run_wake_at_if_unchanged(wake_at, wake_revision)
1654 .await?;
1655 return Err(error).context("receive due run wake reply");
1656 }
1657 };
1658 if let Err(error) = restart_result {
1659 self.ctx
1660 .restore_run_wake_at_if_unchanged(wake_at, wake_revision)
1661 .await?;
1662 return Err(error).context("ensure run handler active for due wake");
1663 }
1664 }
1665 Ok(())
1666 }
1667
1668 fn handle_run_handle_outcome(
1669 &mut self,
1670 outcome: std::result::Result<Result<()>, JoinError>,
1671 ) -> Option<LiveExit> {
1672 self.run_handle = None;
1673 let (clean_exit, crash_message) = match outcome {
1674 Ok(Ok(())) => (true, None),
1675 Ok(Err(error)) => {
1676 log_actor_error(&error, "actor run handler failed");
1677 (false, Some(format!("{error:#}")))
1678 }
1679 Err(error) => {
1680 tracing::error!(?error, "actor run handler join failed");
1681 let message =
1684 (!error.is_cancelled()).then(|| "actor run handler panicked".to_string());
1685 (false, message)
1686 }
1687 };
1688
1689 if clean_exit && self.lifecycle == LifecycleState::Started {
1690 tracing::debug!(
1691 actor_id = %self.ctx.actor_id(),
1692 "actor run handler exited cleanly while awaiting engine stop"
1693 );
1694 return None;
1695 }
1696
1697 if self.lifecycle == LifecycleState::Started {
1698 if let Some(message) = crash_message {
1705 match self.ctx.stop_with_error(message.clone()) {
1706 Ok(()) => return None,
1707 Err(error) => {
1708 if self.ctx.destroy_requested() {
1709 tracing::debug!(
1710 ?error,
1711 actor_id = %self.ctx.actor_id(),
1712 "run handler failed while a stop was already requested"
1713 );
1714 return None;
1715 }
1716 tracing::error!(
1717 ?error,
1718 dropped_message = %message,
1719 actor_id = %self.ctx.actor_id(),
1720 "failed to report run handler error to engine"
1721 );
1722 }
1723 }
1724 }
1725 self.transition_to(LifecycleState::Terminated);
1726 }
1727
1728 self.ctx.reset_sleep_timer();
1729 self.state_save_deadline = None;
1730 self.inspector_serialize_state_deadline = None;
1731 self.close_actor_event_channel();
1732
1733 None
1734 }
1735
1736 async fn wait_for_run_handle(
1737 run_handle: Option<&mut JoinHandle<Result<()>>>,
1738 ) -> std::result::Result<Result<()>, JoinError> {
1739 let Some(run_handle) = run_handle else {
1740 future::pending::<()>().await;
1741 unreachable!();
1742 };
1743 run_handle.await
1744 }
1745
1746 fn close_actor_event_channel(&mut self) {
1747 self.actor_event_tx = None;
1748 self.ctx.configure_actor_events(None);
1749 }
1750
1751 fn start_grace(&mut self, reason: ShutdownKind) {
1752 let grace_period = self.factory.config().effective_sleep_grace_period();
1753 self.sleep_deadline = None;
1754 self.ctx.cancel_sleep_timer();
1755 self.ctx.cancel_actor_abort_signal();
1756 self.sleep_grace = Some(SleepGraceState {
1757 deadline: Instant::now() + grace_period,
1758 reason,
1759 });
1760 self.ctx.reset_sleep_timer();
1761 }
1762
1763 async fn sleep_grace_tick(deadline: Option<Instant>) {
1764 let Some(deadline) = deadline else {
1765 future::pending::<()>().await;
1766 return;
1767 };
1768
1769 sleep_until(deadline).await;
1770 }
1771
1772 async fn on_activity_signal(&mut self) -> Option<LiveExit> {
1773 match self.lifecycle {
1774 LifecycleState::Started => {
1775 self.reset_sleep_deadline().await;
1776 None
1777 }
1778 LifecycleState::SleepGrace | LifecycleState::DestroyGrace => self.try_finish_grace(),
1779 LifecycleState::Loading
1783 | LifecycleState::SleepFinalize
1784 | LifecycleState::Destroying
1785 | LifecycleState::Terminated => None,
1786 }
1787 }
1788
1789 fn try_finish_grace(&mut self) -> Option<LiveExit> {
1790 if self.pending_persistence.is_some() {
1792 return None;
1793 }
1794 let Some(grace) = self.sleep_grace.as_ref() else {
1795 return None;
1796 };
1797 if self.ctx.can_finalize_shutdown(grace.reason) {
1798 let reason = grace.reason;
1799 self.sleep_grace = None;
1800 return Some(LiveExit::Shutdown { reason });
1801 }
1802 None
1803 }
1804
1805 async fn on_sleep_grace_deadline(&mut self) -> Option<LiveExit> {
1806 let Some(grace) = self.sleep_grace.take() else {
1807 return None;
1808 };
1809 if let Some(run_handle) = self.run_handle.as_mut() {
1810 run_handle.abort();
1811 }
1812 self.ctx.cancel_shutdown_deadline();
1813 self.ctx.mark_shutdown_deadline_reached();
1817 self.ctx.record_shutdown_timeout(grace.reason);
1818 tracing::warn!(
1819 actor_id = %self.ctx.actor_id(),
1820 reason = shutdown_reason_label(grace.reason),
1821 deadline_missed_by_ms = Instant::now()
1822 .saturating_duration_since(grace.deadline)
1823 .as_millis() as u64,
1824 core_dispatched_hook_count = self.ctx.core_dispatched_hook_count(),
1825 shutdown_task_count = self.ctx.shutdown_task_count(),
1826 sleep_keep_awake_count = self.ctx.sleep_keep_awake_count(),
1827 sleep_internal_keep_awake_count = self.ctx.sleep_internal_keep_awake_count(),
1828 active_http_request_count = self.ctx.active_http_request_count(),
1829 websocket_callback_count = self.ctx.websocket_callback_count(),
1830 pending_disconnect_count = self.ctx.pending_disconnect_count(),
1831 connection_count = self.ctx.conns().len(),
1832 "actor shutdown reached the grace deadline"
1833 );
1834 Some(LiveExit::Shutdown {
1835 reason: grace.reason,
1836 })
1837 }
1838
1839 async fn join_aborted_run_handle(&mut self) {
1840 let Some(mut run_handle) = self.run_handle.take() else {
1841 return;
1842 };
1843 match (&mut run_handle).await {
1844 Ok(Ok(())) => {}
1845 Ok(Err(error)) => {
1846 log_actor_error(&error, "actor run handler failed during shutdown");
1847 }
1848 Err(error) => {
1849 if !error.is_cancelled() {
1850 tracing::error!(?error, "actor run handler join failed during shutdown");
1851 }
1852 }
1853 };
1854 }
1855
1856 fn log_lifecycle_command_received(&self, command: &'static str, reason: Option<&'static str>) {
1857 tracing::debug!(
1858 actor_id = %self.ctx.actor_id(),
1859 command,
1860 reason,
1861 "actor lifecycle command received"
1862 );
1863 }
1864
1865 fn reply_lifecycle_command(
1866 &self,
1867 command: &'static str,
1868 reason: Option<&'static str>,
1869 reply: oneshot::Sender<Result<()>>,
1870 result: Result<()>,
1871 ) {
1872 let result = result.map_err(|error| self.attach_actor_to_error(error));
1873 let outcome = result_outcome(&result);
1874 let delivered = reply.send(result).is_ok();
1875 tracing::debug!(
1876 actor_id = %self.ctx.actor_id(),
1877 command,
1878 reason,
1879 outcome,
1880 delivered,
1881 "actor lifecycle command replied"
1882 );
1883 }
1884
1885 fn register_shutdown_reply(
1886 &mut self,
1887 command: &'static str,
1888 reason: Option<&'static str>,
1889 reply: oneshot::Sender<Result<()>>,
1890 ) {
1891 if self.shutdown_reply.is_some() {
1892 debug_assert!(false, "engine actor2 sends one Stop per actor instance");
1893 tracing::warn!(
1894 actor_id = %self.ctx.actor_id(),
1895 command,
1896 reason,
1897 "duplicate Stop after shutdown reply was registered, dropping new reply"
1898 );
1899 return;
1900 }
1901 self.shutdown_reply = Some(PendingLifecycleReply {
1902 command,
1903 reason,
1904 reply,
1905 });
1906 }
1907
1908 fn deliver_shutdown_reply(&mut self, reason: ShutdownKind, result: &Result<()>) {
1909
1910
1911 let Some(pending) = self.shutdown_reply.take() else {
1912 return;
1913 };
1914 let outcome = result_outcome(result);
1915 let delivered = pending.reply.send(clone_shutdown_result(result)).is_ok();
1916 tracing::debug!(
1917 actor_id = %self.ctx.actor_id(),
1918 command = pending.command,
1919 reason = pending.reason,
1920 shutdown_reason = shutdown_reason_label(reason),
1921 outcome,
1922 delivered,
1923 "actor lifecycle command replied"
1924 );
1925 }
1926
1927 async fn run_shutdown(&mut self, reason: ShutdownKind) -> Result<()> {
1928 self.sleep_grace = None;
1929 let started_at = Instant::now();
1930 self.state_save_deadline = None;
1931 self.inspector_serialize_state_deadline = None;
1932 self.sleep_deadline = None;
1933 self.transition_to(match reason {
1934 ShutdownKind::Sleep => LifecycleState::SleepFinalize,
1935 ShutdownKind::Destroy => LifecycleState::Destroying,
1936 });
1937 self.pending_inspector = None;
1940 self.finish_pending_persistence().await;
1941 self.drain_deferred_workflow_flushes().await;
1944 self.explicit_transaction_wait = None;
1945 let result: Result<()> = async {
1946 self.ctx.close_state_transactions().await;
1947 if let Err(error) = self.ctx.sql().rollback_manual_transaction().await {
1948 tracing::error!(
1949 actor_id = %self.ctx.actor_id(),
1950 ?error,
1951 "failed to roll back manual sqlite transaction before the final state save"
1952 );
1953 }
1954 let final_save = self.save_final_state().await;
1955 self.ctx.fail_unpersisted_save_requests();
1957 final_save?;
1958 self.close_actor_event_channel();
1959 self.join_aborted_run_handle().await;
1960 Self::finish_shutdown_cleanup_with_ctx(self.ctx.clone(), reason).await
1961 }
1962 .await;
1963 if result.is_ok() && matches!(reason, ShutdownKind::Destroy) {
1964 self.ctx.mark_destroy_completed();
1965 }
1966 self.ctx.record_shutdown_wait(reason, started_at.elapsed());
1967 result
1968 }
1969
1970 async fn finish_failed_startup(&mut self) {
1971 self.ctx.cancel_actor_abort_signal();
1972 self.ctx.cancel_sleep_timer();
1973 self.ctx.suspend_alarm_dispatch();
1974 self.ctx.cancel_local_alarm_timeouts();
1975 self.ctx.set_local_alarm_callback(None);
1976 self.ctx.configure_lifecycle_events(None);
1977 self.close_actor_event_channel();
1978 if let Some(run_handle) = self.run_handle.as_mut() {
1979 run_handle.abort();
1980 }
1981 self.join_aborted_run_handle().await;
1982 self.ctx.mark_shutdown_deadline_reached();
1985 self.ctx.teardown_sleep_state().await;
1986 self.ctx.wait_for_pending_state_writes().await;
1987 self.ctx.abandon_pending_alarm_writes();
1988 #[cfg(feature = "sqlite-local")]
1989 self.ctx.shutdown_actor_runtime_socket().await;
1990 if let Err(error) = self.ctx.sql().close_and_wait().await {
1991 tracing::error!(
1992 actor_id = %self.ctx.actor_id(),
1993 ?error,
1994 "actor resource cleanup failed after startup failure",
1995 );
1996 }
1997 self.ctx.sql().request_allocator_trim();
1998 self.transition_to(LifecycleState::Terminated);
1999 }
2000
2001 async fn save_final_state(&mut self) -> Result<()> {
2002 let save_request_revision = self.ctx.save_request_revision();
2005 let (reply_tx, reply_rx) = oneshot::channel();
2006 if let Err(error) = self.send_actor_event(
2007 "shutdown_serialize_state",
2008 ActorEvent::SerializeState {
2009 reason: SerializeStateReason::Save,
2010 reply: Reply::from(reply_tx),
2011 },
2012 ) {
2013 tracing::error!(?error, "shutdown serialize-state enqueue failed");
2014 return Ok(());
2015 }
2016
2017 let cap = self.shutdown_serialize_cap();
2020 let deltas = match timeout(cap, reply_rx).await {
2021 Ok(Ok(Ok(deltas))) => deltas,
2022 Ok(Ok(Err(error))) => {
2023 tracing::error!(?error, "serializeState callback returned error");
2024 return Ok(());
2025 }
2026 Ok(Err(error)) => {
2027 tracing::warn!(?error, "serializeState reply dropped");
2028 return Ok(());
2029 }
2030 Err(_) => {
2031 tracing::warn!(
2032 actor_id = %self.ctx.actor_id(),
2033 cap_ms = cap.as_millis() as u64,
2034 "serializeState timed out; prior persisted state retained"
2035 );
2036 return Ok(());
2037 }
2038 };
2039 if deltas.is_empty() && self.unpersisted_serialization {
2040 tracing::warn!(
2041 actor_id = %self.ctx.actor_id(),
2042 "final serialization is empty after an unwritten save; prior persisted state retained"
2043 );
2044 return Ok(());
2045 }
2046
2047 self.ctx
2048 .save_state_with_revision(deltas, save_request_revision)
2049 .await
2050 }
2051
2052 async fn finish_shutdown_cleanup_with_ctx(
2053 ctx: ActorContext,
2054 reason: ShutdownKind,
2055 ) -> Result<()> {
2056 let reason_label = shutdown_reason_label(reason);
2057 let actor_id = ctx.actor_id().to_owned();
2058 ctx.teardown_sleep_state().await;
2059 tracing::debug!(
2060 actor_id = %actor_id,
2061 reason = reason_label,
2062 step = "teardown_sleep_state",
2063 "actor shutdown cleanup step completed"
2064 );
2065
2066 ctx.wait_for_pending_state_writes().await;
2067 tracing::debug!(
2068 actor_id = %actor_id,
2069 reason = reason_label,
2070 step = "wait_for_pending_state_writes",
2071 "actor shutdown cleanup step completed"
2072 );
2073 match reason {
2077 ShutdownKind::Sleep => {
2078 ctx.sync_alarm_before_sleep_logged().await;
2079 tracing::debug!(
2080 actor_id = %actor_id,
2081 reason = reason_label,
2082 step = "sync_alarm",
2083 "actor shutdown cleanup step completed"
2084 );
2085 }
2086 ShutdownKind::Destroy => {
2087 ctx.cancel_driver_alarm_logged();
2088 tracing::debug!(
2089 actor_id = %actor_id,
2090 reason = reason_label,
2091 step = "cancel_driver_alarm",
2092 "actor shutdown cleanup step completed"
2093 );
2094 }
2095 }
2096 ctx.wait_for_pending_alarm_writes().await;
2097 tracing::debug!(
2098 actor_id = %actor_id,
2099 reason = reason_label,
2100 step = "wait_for_pending_alarm_writes",
2101 "actor shutdown cleanup step completed"
2102 );
2103 #[cfg(feature = "sqlite-local")]
2104 ctx.shutdown_actor_runtime_socket().await;
2105 ctx.sql()
2106 .cleanup_for_shutdown(reason == ShutdownKind::Sleep)
2107 .await
2108 .with_context(|| format!("cleanup sqlite during {reason_label} shutdown"))?;
2109 ctx.sql().request_allocator_trim();
2112 tracing::debug!(
2113 actor_id = %actor_id,
2114 reason = reason_label,
2115 step = "cleanup_sqlite",
2116 "actor shutdown cleanup step completed"
2117 );
2118 match reason {
2119 ShutdownKind::Sleep => {
2123 ctx.cancel_local_alarm_timeouts();
2124 tracing::debug!(
2125 actor_id = %actor_id,
2126 reason = reason_label,
2127 step = "cancel_local_alarm_timeouts",
2128 "actor shutdown cleanup step completed"
2129 );
2130 }
2131 ShutdownKind::Destroy => {}
2132 }
2133 Ok(())
2134 }
2135
2136 fn record_inbox_depths(&self) {
2137 self.ctx
2138 .metrics()
2139 .set_lifecycle_inbox_depth(self.lifecycle_inbox.len());
2140 self.ctx
2141 .metrics()
2142 .set_dispatch_inbox_depth(self.dispatch_inbox.len());
2143 self.ctx
2144 .metrics()
2145 .set_lifecycle_event_inbox_depth(self.lifecycle_events.len());
2146 }
2147
2148 fn accepting_dispatch(&self) -> bool {
2149 matches!(
2150 self.lifecycle,
2151 LifecycleState::Started | LifecycleState::SleepGrace | LifecycleState::DestroyGrace
2152 )
2153 }
2154
2155 fn sleep_timer_active(&self) -> bool {
2156 self.sleep_deadline.is_some()
2157 }
2158
2159 fn state_save_timer_active(&self) -> bool {
2160 self.state_save_deadline.is_some() && self.pending_persistence.is_none()
2161 }
2162
2163 fn inspector_serialize_timer_active(&self) -> bool {
2164 self.inspector_serialize_state_deadline.is_some()
2165 && self.pending_persistence.is_none()
2166 && self.pending_inspector.is_none()
2167 }
2168
2169 fn schedule_state_save(&mut self, immediate: bool) {
2170 if !matches!(
2171 self.lifecycle,
2172 LifecycleState::Started | LifecycleState::SleepGrace
2173 ) || !self.ctx.save_requested()
2174 {
2175 self.state_save_deadline = None;
2176 return;
2177 }
2178
2179 let next_deadline = self.ctx.save_deadline(immediate);
2180 self.state_save_deadline = Some(match self.state_save_deadline {
2181 Some(existing) => existing.min(next_deadline),
2182 None => next_deadline,
2183 });
2184 }
2185
2186 async fn sleep_tick(deadline: Option<Instant>) {
2187 let Some(deadline) = deadline else {
2188 future::pending::<()>().await;
2189 return;
2190 };
2191
2192 sleep_until(deadline).await;
2193 }
2194
2195 async fn state_save_tick(deadline: Option<Instant>) {
2196 let Some(deadline) = deadline else {
2197 future::pending::<()>().await;
2198 return;
2199 };
2200
2201 sleep_until(deadline).await;
2202 }
2203
2204 async fn explicit_transaction_finished(wait: Option<&mut watch::Receiver<bool>>) {
2207 let Some(wait) = wait else {
2208 future::pending::<()>().await;
2209 return;
2210 };
2211
2212 let _ = wait.wait_for(|open| !*open).await;
2213 }
2214
2215 fn on_explicit_transaction_finished(&mut self) {
2216 self.explicit_transaction_wait = None;
2217 if self.ctx.save_requested() {
2218 self.schedule_state_save(self.ctx.save_requested_immediate());
2219 self.sync_inspector_serialize_deadline();
2220 }
2221 }
2222
2223 async fn inspector_serialize_state_tick(deadline: Option<Instant>) {
2224 let Some(deadline) = deadline else {
2225 future::pending::<()>().await;
2226 return;
2227 };
2228
2229 sleep_until(deadline).await;
2230 }
2231
2232 fn start_state_save(&mut self) {
2233 if self.pending_persistence.is_some() {
2234 return;
2235 }
2236 self.state_save_deadline = None;
2237 self.inspector_serialize_state_deadline = None;
2238 if !matches!(
2239 self.lifecycle,
2240 LifecycleState::Started | LifecycleState::SleepGrace
2241 ) || !self.ctx.save_requested()
2242 {
2243 return;
2244 }
2245 if self.ctx.state_transaction_active() {
2249 return;
2250 }
2251 if let Some(mut explicit_transaction) = self.ctx.sql().subscribe_explicit_transaction()
2256 && *explicit_transaction.borrow_and_update()
2257 {
2258 tracing::debug!(
2259 actor_id = %self.ctx.actor_id(),
2260 "sqlite transaction is open, deferring actor state save until autocommit"
2261 );
2262 self.explicit_transaction_wait = Some(explicit_transaction);
2263 return;
2264 }
2265
2266 let save_request_revision = self.ctx.save_request_revision();
2267 let state_transaction_epoch = self.ctx.state_transaction_epoch();
2268 let (reply_tx, reply_rx) = oneshot::channel();
2269 match self.send_actor_event(
2270 "save_tick",
2271 ActorEvent::SerializeState {
2272 reason: SerializeStateReason::Save,
2273 reply: Reply::from(reply_tx),
2274 },
2275 ) {
2276 Ok(()) => {}
2277 Err(error) => {
2278 tracing::warn!(?error, "failed to enqueue save tick");
2279 self.schedule_state_save(true);
2280 return;
2281 }
2282 }
2283
2284 let ctx = self.ctx.clone();
2285 let serialize_cap = self.shutdown_serialize_cap();
2286 self.pending_persistence = Some(PendingPersistence {
2287 completion: PersistenceCompletion::StateSave,
2288 future: boxed_runtime_future(async move {
2289 let deltas = await_save_serialization(
2292 ctx.shutdown_deadline_token(),
2293 reply_rx,
2294 serialize_cap,
2295 "actor save tick reply dropped",
2296 )
2297 .await?;
2298 let serialized_bytes = state_delta_payload_bytes(&deltas);
2299 tracing::debug!(
2300 actor_id = %ctx.actor_id(),
2301 reason = SerializeStateReason::Save.label(),
2302 delta_count = deltas.len(),
2303 serialized_bytes,
2304 save_request_revision,
2305 "actor serializeState completed"
2306 );
2307 ctx.save_state_with_revision_at_transaction_epoch(
2310 deltas,
2311 save_request_revision,
2312 state_transaction_epoch,
2313 )
2314 .await
2315 }),
2316 });
2317 }
2318
2319 async fn wait_for_persistence(save: Option<&mut PendingPersistence>) -> Result<bool> {
2320 let Some(save) = save else {
2321 return future::pending().await;
2322 };
2323 (&mut save.future).await
2324 }
2325
2326 fn on_state_save_finished(&mut self, outcome: Result<bool>) {
2327 match outcome {
2328 Ok(true) => {
2329 if self.ctx.save_requested() {
2330 self.schedule_state_save(self.ctx.save_requested_immediate());
2331 self.sync_inspector_serialize_deadline();
2332 }
2333 }
2334 Ok(false) => {
2335 self.ctx.request_save(RequestSaveOpts {
2336 immediate: true,
2337 max_wait_ms: None,
2338 });
2339 self.schedule_state_save(true);
2340 self.sync_inspector_serialize_deadline();
2341 }
2342 Err(error) => {
2343 tracing::error!(?error, "failed to persist actor save tick");
2344 self.request_resave_after_failed_persistence();
2345 }
2346 }
2347 }
2348
2349 fn request_resave_after_failed_persistence(&mut self) {
2355 self.ctx.throttle_failed_save_request();
2356 self.ctx.request_save(RequestSaveOpts {
2357 immediate: false,
2358 max_wait_ms: None,
2359 });
2360 self.schedule_state_save(false);
2361 self.sync_inspector_serialize_deadline();
2362 }
2363
2364 fn shutdown_serialize_cap(&self) -> Duration {
2368 SERIALIZE_STATE_SHUTDOWN_SANITY_CAP
2369 .max(self.factory.config().effective_sleep_grace_period())
2370 }
2371
2372 async fn finish_pending_persistence(&mut self) {
2373 if let Some(save) = self.pending_persistence.take() {
2374 let outcome = save.future.await;
2375 if outcome.is_err() {
2376 self.unpersisted_serialization = true;
2379 }
2380 self.on_persistence_finished(save.completion, outcome);
2381 }
2382 }
2383
2384 fn start_inspector_serialize_state(&mut self) {
2385 if self.pending_inspector.is_some() {
2386 return;
2387 }
2388 self.inspector_serialize_state_deadline = None;
2389 if !matches!(
2390 self.lifecycle,
2391 LifecycleState::Started | LifecycleState::SleepGrace
2392 ) || self.inspector_attach_count.load(Ordering::SeqCst) == 0
2393 || !self.ctx.save_requested()
2394 {
2395 return;
2396 }
2397
2398 let (reply_tx, reply_rx) = oneshot::channel();
2399 match self.send_actor_event(
2400 "inspector_serialize_state",
2401 ActorEvent::SerializeState {
2402 reason: SerializeStateReason::Inspector,
2403 reply: Reply::from(reply_tx),
2404 },
2405 ) {
2406 Ok(()) => {}
2407 Err(error) => {
2408 tracing::warn!(?error, "failed to enqueue inspector serialize tick");
2409 self.sync_inspector_serialize_deadline();
2410 return;
2411 }
2412 }
2413
2414 let deadline = self.ctx.shutdown_deadline_token();
2415 self.pending_inspector = Some(boxed_runtime_future(async move {
2416 tokio::select! {
2417 biased;
2418 _ = deadline.cancelled() => Err(ActorLifecycleError::NotReady.build()),
2419 reply = reply_rx => reply.context("actor inspector serialize tick reply dropped")?,
2420 }
2421 }));
2422 }
2423
2424 async fn wait_for_inspector(
2425 save: Option<&mut RuntimeBoxFuture<Result<Vec<StateDelta>>>>,
2426 ) -> Result<Vec<StateDelta>> {
2427 let Some(save) = save else {
2428 return future::pending().await;
2429 };
2430 save.await
2431 }
2432
2433 fn on_inspector_finished(&mut self, outcome: Result<Vec<StateDelta>>) {
2434 match outcome {
2435 Ok(deltas) => {
2436 tracing::debug!(
2437 actor_id = %self.ctx.actor_id(),
2438 reason = SerializeStateReason::Inspector.label(),
2439 delta_count = deltas.len(),
2440 serialized_bytes = state_delta_payload_bytes(&deltas),
2441 "actor serializeState completed"
2442 );
2443 self.broadcast_inspector_overlay(&deltas);
2444 }
2445 Err(error) => {
2446 tracing::error!(?error, "actor inspector serialize tick failed");
2447 self.sync_inspector_serialize_deadline();
2448 }
2449 }
2450 }
2451
2452 async fn on_sleep_tick(&mut self) {
2453 self.sleep_deadline = None;
2454 if self.lifecycle != LifecycleState::Started {
2455 return;
2456 }
2457
2458 let can_sleep = self.ctx.can_sleep().await;
2459 if can_sleep == crate::actor::sleep::CanSleep::Yes {
2460 tracing::debug!(
2461 actor_id = %self.ctx.actor_id(),
2462 sleep_timeout_ms = self.factory.config().sleep_timeout.as_millis() as u64,
2463 "sleep idle deadline elapsed"
2464 );
2465 if let Err(err) = self.ctx.sleep() {
2466 tracing::debug!(
2467 actor_id = %self.ctx.actor_id(),
2468 ?err,
2469 "sleep idle deadline request suppressed"
2470 );
2471 }
2472 } else {
2473 tracing::warn!(
2474 actor_id = %self.ctx.actor_id(),
2475 reason = ?can_sleep,
2476 "sleep idle deadline elapsed but actor stayed awake"
2477 );
2478 self.reset_sleep_deadline().await;
2479 }
2480 }
2481
2482 async fn reset_sleep_deadline(&mut self) {
2483 if self.lifecycle != LifecycleState::Started {
2484 self.sleep_deadline = None;
2485 tracing::debug!(
2486 actor_id = %self.ctx.actor_id(),
2487 lifecycle = ?self.lifecycle,
2488 "sleep activity reset skipped outside started state"
2489 );
2490 return;
2491 }
2492
2493 let can_sleep = self.ctx.can_sleep().await;
2494 if can_sleep == crate::actor::sleep::CanSleep::Yes {
2495 let deadline = Instant::now() + self.factory.config().sleep_timeout;
2496 self.sleep_deadline = Some(deadline);
2497 tracing::debug!(
2498 actor_id = %self.ctx.actor_id(),
2499 sleep_timeout_ms = self.factory.config().sleep_timeout.as_millis() as u64,
2500 "sleep activity reset"
2501 );
2502 } else {
2503 self.sleep_deadline = None;
2504 tracing::debug!(
2505 actor_id = %self.ctx.actor_id(),
2506 reason = ?can_sleep,
2507 "sleep activity reset skipped"
2508 );
2509 }
2510 }
2511
2512 fn sync_inspector_serialize_deadline(&mut self) {
2513 if !matches!(
2514 self.lifecycle,
2515 LifecycleState::Started | LifecycleState::SleepGrace
2516 ) || self.inspector_attach_count.load(Ordering::SeqCst) == 0
2517 || !self.ctx.save_requested()
2518 {
2519 self.inspector_serialize_state_deadline = None;
2520 return;
2521 }
2522
2523 self.inspector_serialize_state_deadline
2524 .get_or_insert_with(|| Instant::now() + INSPECTOR_SERIALIZE_STATE_INTERVAL);
2525 }
2526
2527 fn broadcast_inspector_overlay(&self, deltas: &[StateDelta]) {
2528 if self.inspector_attach_count.load(Ordering::SeqCst) == 0 || deltas.is_empty() {
2529 return;
2530 }
2531
2532 let mut payload = Vec::new();
2533 if let Err(error) = ciborium::into_writer(deltas, &mut payload) {
2534 tracing::error!(?error, "failed to encode inspector overlay deltas");
2535 return;
2536 }
2537
2538 let payload = Arc::new(payload);
2539 let payload_bytes = payload.len();
2540 match self.inspector_overlay_tx.send(payload) {
2541 Ok(receiver_count) => {
2542 tracing::debug!(
2543 actor_id = %self.ctx.actor_id(),
2544 delta_count = deltas.len(),
2545 payload_bytes,
2546 receiver_count,
2547 "inspector overlay broadcast"
2548 );
2549 }
2550 Err(error) => {
2551 tracing::debug!(
2552 actor_id = %self.ctx.actor_id(),
2553 delta_count = deltas.len(),
2554 payload_bytes,
2555 error = ?error,
2556 "inspector overlay broadcast dropped"
2557 );
2558 }
2559 }
2560 }
2561
2562 fn should_terminate(&self) -> bool {
2563 matches!(self.lifecycle, LifecycleState::Terminated)
2564 }
2565
2566 fn log_closed_channel(&self, channel: &'static str, message: &'static str) {
2567 tracing::warn!(
2568 actor_id = %self.ctx.actor_id(),
2569 channel,
2570 reason = "all senders dropped",
2571 "{message}"
2572 );
2573 }
2574
2575 fn transition_to(&mut self, lifecycle: LifecycleState) {
2576 let old = self.lifecycle;
2577 tracing::info!(
2578 actor_id = %self.ctx.actor_id(),
2579 old = ?old,
2580 new = ?lifecycle,
2581 "actor lifecycle transition"
2582 );
2583 self.lifecycle = lifecycle;
2584 if matches!(lifecycle, LifecycleState::Started) {
2585 self.ctx.reset_abort_signal_for_start();
2588 self.ctx.clear_sleep_requested();
2589 }
2590 self.ctx.set_started(matches!(
2591 lifecycle,
2592 LifecycleState::Started | LifecycleState::SleepGrace
2593 ));
2594 }
2595}
2596
2597fn shutdown_reason_label(reason: ShutdownKind) -> &'static str {
2598 match reason {
2599 ShutdownKind::Sleep => "sleep",
2600 ShutdownKind::Destroy => "destroy",
2601 }
2602}
2603
2604fn clone_shutdown_result(result: &Result<()>) -> Result<()> {
2605 match result {
2606 Ok(()) => Ok(()),
2607 Err(error) => {
2608 let error = rivet_error::RivetError::extract(error);
2609 Err(anyhow::Error::new(error))
2610 }
2611 }
2612}
2613
2614fn log_actor_error(error: &anyhow::Error, log_message: &'static str) {
2615 let structured = rivet_error::RivetError::extract(error);
2616 tracing::error!(
2617 ?error,
2618 group = structured.group(),
2619 code = structured.code(),
2620 message = %structured.message(),
2621 metadata = ?structured.metadata(),
2622 "{log_message}"
2623 );
2624}
2625
2626fn result_outcome<T>(result: &Result<T>) -> &'static str {
2627 match result {
2628 Ok(_) => "ok",
2629 Err(_) => "error",
2630 }
2631}
2632
2633struct FailUnpersistedSavesOnDrop(ActorContext);
2635
2636impl Drop for FailUnpersistedSavesOnDrop {
2637 fn drop(&mut self) {
2638 self.0.fail_unpersisted_save_requests();
2639 }
2640}
2641
2642async fn await_save_serialization(
2647 shutdown_deadline: CancellationToken,
2648 mut reply_rx: oneshot::Receiver<Result<Vec<StateDelta>>>,
2649 cap: Duration,
2650 dropped_context: &'static str,
2651) -> Result<Vec<StateDelta>> {
2652 let reply = tokio::select! {
2653 biased;
2654 reply = &mut reply_rx => reply,
2655 _ = shutdown_deadline.cancelled() => match timeout(cap, &mut reply_rx).await {
2656 Ok(reply) => reply,
2657 Err(_) => return Err(ActorLifecycleError::ShutdownTimeout.build()),
2658 },
2659 };
2660 reply.context(dropped_context)?
2661}
2662
2663fn state_delta_payload_bytes(deltas: &[StateDelta]) -> usize {
2664 deltas.iter().map(StateDelta::payload_len).sum()
2665}
2666
2667fn duration_ms_f64(duration: Duration) -> f64 {
2668 duration.as_secs_f64() * 1000.0
2669}
2670
2671fn spawn_inspector_mirror(ctx: &ActorContext, token: String) {
2675 let task_ctx = ctx.clone();
2676 ctx.wait_until(async move {
2677 let started_at = Instant::now();
2678 let result =
2679 crate::inspector::auth::persist_inspector_token_mirror(&task_ctx, &token).await;
2680 let outcome = match &result {
2681 Ok(()) => "success",
2682 Err(_) => "error",
2683 };
2684 task_ctx.metrics().observe_startup_phase(
2685 StartupPhase::InspectorToken,
2686 Some(true),
2687 outcome,
2688 started_at.elapsed(),
2689 );
2690 if let Err(error) = result {
2691 tracing::warn!(
2692 actor_id = %task_ctx.actor_id(),
2693 ?error,
2694 "inspector token mirror write failed, the next start repairs it"
2695 );
2696 }
2697 });
2698}
2699
2700fn spawn_inspector_mirror_check(ctx: &ActorContext, token: String) {
2703 let task_ctx = ctx.clone();
2704 ctx.wait_until(async move {
2705 if let Err(error) =
2706 crate::inspector::auth::ensure_inspector_token_mirror(&task_ctx, &token).await
2707 {
2708 tracing::warn!(
2709 actor_id = %task_ctx.actor_id(),
2710 ?error,
2711 "inspector token mirror check failed, the next start checks it again"
2712 );
2713 }
2714 });
2715}