1use std::sync::atomic::Ordering;
2use std::sync::{Arc, Weak};
3use std::time::Duration;
4
5use crate::time::Instant as StdInstant;
6use crate::time::sleep;
7
8use anyhow::{Context, Result};
9use rivetkit_actor_persist::{generated::v4 as persist_v4, versioned as persist_versioned};
10#[cfg(not(feature = "wasm-runtime"))]
11use tokio::runtime::Handle;
12use tokio::sync::{Mutex as AsyncMutex, Notify, OwnedMutexGuard, mpsc, oneshot};
13use tokio::task::JoinHandle;
14
15use tracing::Instrument;
16
17use crate::actor::connection::{PersistedConnection, decode_persisted_connection};
18use crate::actor::context::ActorContext;
19use crate::actor::internal_storage;
20use crate::actor::lifecycle_hooks::Reply;
21use crate::actor::messages::{StateDelta, WorkflowKvWrite};
22use crate::actor::persist::decode_latest_with_embedded_version;
23
24use crate::actor::task::LifecycleEvent;
25use crate::actor::task_types::StateMutationReason;
26use crate::error::{ActorLifecycle, ActorRuntime};
27use crate::runtime::RuntimeSpawner;
28use crate::sqlite::{BindParam, ExecuteResult, SqliteTransaction};
29use crate::types::SaveStateOpts;
30
31
32
33pub type PersistedScheduleEvent = persist_v4::ScheduleEvent;
34pub type PersistedActor = persist_v4::Actor;
35
36
37
38pub(crate) fn decode_persisted_actor(payload: &[u8]) -> Result<PersistedActor> {
39 let actor = decode_latest_with_embedded_version::<persist_versioned::Actor>(
40 payload,
41 "persisted actor",
42 )?;
43 Ok(actor)
44}
45
46
47
48pub(crate) fn decode_last_pushed_alarm(payload: &[u8]) -> Result<Option<i64>> {
49 decode_latest_with_embedded_version::<persist_versioned::LastPushedAlarm>(
50 payload,
51 "last pushed alarm",
52 )
53}
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub struct RequestSaveOpts {
57 pub immediate: bool,
58 pub max_wait_ms: Option<u32>,
59}
60
61pub(super) struct PendingSave {
62 scheduled_at: StdInstant,
63 handle: JoinHandle<()>,
64}
65
66pub struct OnStateChangeGuard {
67 ctx: Option<ActorContext>,
68}
69#[derive(Clone)]
72pub struct ActorStateTransaction {
73 owner: Arc<AsyncMutex<ActorStateTransactionOwner>>,
74}
75
76pub(super) enum StateTransactionSlot {
83 Idle,
85 Beginning,
89 Open(Weak<AsyncMutex<ActorStateTransactionOwner>>),
92 Closed,
96}
97
98struct StateSaveExclusion {
105 ctx: ActorContext,
106 save_guard: Option<OwnedMutexGuard<()>>,
107}
108
109impl Drop for StateSaveExclusion {
110 fn drop(&mut self) {
111 {
112 let mut slot = self.ctx.0.state_transaction.lock();
113 match *slot {
114 StateTransactionSlot::Closed => {}
115 StateTransactionSlot::Idle
116 | StateTransactionSlot::Beginning
117 | StateTransactionSlot::Open(_) => *slot = StateTransactionSlot::Idle,
118 }
119 }
120
121 self.save_guard.take();
123 self.ctx.notify_state_transaction_finished();
124 }
125}
126
127pub(super) struct ActorStateTransactionOwner {
128 ctx: ActorContext,
129 transaction: SqliteTransaction,
130 save_exclusion: Option<StateSaveExclusion>,
133 write_guard: Option<InFlightWrite>,
134 finalized: bool,
135 committed: bool,
136 save_request_revision: u64,
137 statement_count: usize,
138 bind_payload_bytes: usize,
139 rollback_connection_states: Vec<(String, Vec<u8>)>,
140}
141
142impl ActorStateTransactionOwner {
143 fn restore_connection_states(&self) {
144 for (conn_id, state) in &self.rollback_connection_states {
145 if let Some(conn) = self.ctx.connection(conn_id) {
146 conn.set_state(state.clone());
147 }
148 }
149 }
150
151 fn advance_epoch(&self) {
152 self.ctx
153 .0
154 .state_transaction_epoch
155 .fetch_add(1, Ordering::SeqCst);
156 }
157}
158
159impl Drop for ActorStateTransactionOwner {
160 fn drop(&mut self) {
161 if !self.finalized {
162 self.restore_connection_states();
163 self.advance_epoch();
164 let transaction = self.transaction.clone();
165 #[cfg(not(feature = "wasm-runtime"))]
166 if let Ok(handle) = Handle::try_current() {
167 handle.spawn(async move {
168 if let Err(error) = transaction.rollback().await {
169 tracing::debug!(?error, "dropped actor state transaction rollback failed");
170 }
171 });
172 }
173 #[cfg(feature = "wasm-runtime")]
174 wasm_bindgen_futures::spawn_local(async move {
175 if let Err(error) = transaction.rollback().await {
176 tracing::debug!(?error, "dropped actor state transaction rollback failed");
177 }
178 });
179 }
180 if !self.committed {
181 self.ctx.schedule_save(None);
182 }
183 }
184}
185
186impl ActorStateTransaction {
187 pub async fn execute(
188 &self,
189 sql: impl Into<String>,
190 params: Option<Vec<BindParam>>,
191 ) -> Result<ExecuteResult> {
192 let mut owner = self.owner.lock().await;
193 if owner.finalized {
194 return Err(anyhow::anyhow!(
195 "actor state transaction is already finalized"
196 ));
197 }
198 let sql = sql.into();
199 if is_transaction_terminator(&sql) {
200 return Err(anyhow::anyhow!(
201 "cannot execute transaction terminator inside an actor state transaction; use the transaction commit or rollback method"
202 ));
203 }
204 let payload_bytes = match params.as_deref() {
205 Some(params) => params
206 .iter()
207 .map(internal_storage::bind_param_payload_len)
208 .fold(0usize, usize::saturating_add),
209 None => sql.len(),
210 };
211 let result = owner.transaction.execute(sql, params).await?;
212 owner.statement_count = owner.statement_count.saturating_add(1);
213 owner.bind_payload_bytes = owner.bind_payload_bytes.saturating_add(payload_bytes);
214 Ok(result)
215 }
216
217 pub async fn commit(&self, deltas: Vec<StateDelta>) -> Result<()> {
218 let mut owner = self.owner.lock().await;
219 if owner.finalized {
220 return Err(anyhow::anyhow!(
221 "actor state transaction is already finalized"
222 ));
223 }
224
225 let save_request_revision = owner.save_request_revision;
226 let transaction = owner.transaction.clone();
227 let statement_count = owner.statement_count;
228 let bind_payload_bytes = owner.bind_payload_bytes;
229 let result = owner
230 .ctx
231 .commit_state_transaction(
232 &transaction,
233 deltas,
234 save_request_revision,
235 statement_count,
236 bind_payload_bytes,
237 )
238 .await;
239 owner.finalized = true;
240 owner.committed = result.is_ok();
241 if result.is_err() {
242 owner.restore_connection_states();
243 }
244 owner.advance_epoch();
245 owner.write_guard.take();
246 owner.save_exclusion.take();
247 if result.is_err() {
248 owner.ctx.schedule_save(None);
249 }
250 result
251 }
252
253 pub async fn rollback(&self) -> Result<()> {
254 let mut owner = self.owner.lock().await;
255 if owner.finalized {
256 return Err(anyhow::anyhow!(
257 "actor state transaction is already finalized"
258 ));
259 }
260 let result = owner.transaction.rollback().await;
261 owner.finalized = true;
262 owner.restore_connection_states();
263 owner.advance_epoch();
264 owner.write_guard.take();
265 owner.save_exclusion.take();
266 owner.ctx.schedule_save(None);
267 result
268 }
269}
270
271fn is_transaction_terminator(sql: &str) -> bool {
272 let mut offset = 0;
273 let Some(first) = next_sql_keyword(sql, &mut offset) else {
274 return false;
275 };
276 if first.eq_ignore_ascii_case("COMMIT") || first.eq_ignore_ascii_case("END") {
277 return true;
278 }
279 if !first.eq_ignore_ascii_case("ROLLBACK") {
280 return false;
281 }
282
283 let mut next = next_sql_keyword(sql, &mut offset);
284 if next.is_some_and(|keyword| keyword.eq_ignore_ascii_case("TRANSACTION")) {
285 next = next_sql_keyword(sql, &mut offset);
286 }
287 !next.is_some_and(|keyword| keyword.eq_ignore_ascii_case("TO"))
288}
289
290fn next_sql_keyword<'a>(sql: &'a str, offset: &mut usize) -> Option<&'a str> {
291 let bytes = sql.as_bytes();
292 loop {
293 while bytes.get(*offset).is_some_and(u8::is_ascii_whitespace) {
294 *offset += 1;
295 }
296 if bytes.get(*offset..*offset + 2) == Some(b"--") {
297 *offset += 2;
298 while bytes.get(*offset).is_some_and(|byte| *byte != b'\n') {
299 *offset += 1;
300 }
301 continue;
302 }
303 if bytes.get(*offset..*offset + 2) == Some(b"/*") {
304 *offset += 2;
305 while bytes.get(*offset..*offset + 2) != Some(b"*/") {
306 bytes.get(*offset)?;
307 *offset += 1;
308 }
309 *offset += 2;
310 continue;
311 }
312 break;
313 }
314
315 let start = *offset;
316 while bytes.get(*offset).is_some_and(u8::is_ascii_alphabetic) {
317 *offset += 1;
318 }
319 (start != *offset).then(|| &sql[start..*offset])
320}
321
322impl OnStateChangeGuard {
323 fn new(ctx: ActorContext) -> Self {
324 ctx.on_state_change_started();
325 Self { ctx: Some(ctx) }
326 }
327}
328
329impl Drop for OnStateChangeGuard {
330 fn drop(&mut self) {
331 if let Some(ctx) = self.ctx.take() {
332 ctx.on_state_change_finished();
333 }
334 }
335}
336
337impl ActorContext {
338 pub async fn begin_state_transaction(
339 &self,
340 timeout: Option<Duration>,
341 ) -> Result<ActorStateTransaction> {
342 self.clear_pending_save();
343 let save_guard = Arc::clone(&self.0.save_guard).lock_owned().await;
344 {
345 let mut slot = self.0.state_transaction.lock();
347 match *slot {
348 StateTransactionSlot::Closed => {
349 return Err(ActorLifecycle::Stopping.build());
350 }
351 StateTransactionSlot::Idle
352 | StateTransactionSlot::Beginning
353 | StateTransactionSlot::Open(_) => *slot = StateTransactionSlot::Beginning,
354 }
355 }
356 let save_exclusion = StateSaveExclusion {
357 ctx: self.clone(),
358 save_guard: Some(save_guard),
359 };
360 self.0
361 .state_transaction_epoch
362 .fetch_add(1, Ordering::SeqCst);
363 self.wait_for_in_flight_writes().await;
364 let rollback_connection_states = self
365 .iter_connections()
366 .filter(|conn| conn.is_hibernatable())
367 .map(|conn| (conn.id().to_owned(), conn.state()))
368 .collect();
369 let sql = self.invocation_sql();
370 let transaction = match sql.begin_transaction(timeout).await {
371 Ok(transaction) => transaction,
372 Err(error) => {
373 self.0
374 .state_transaction_epoch
375 .fetch_add(1, Ordering::SeqCst);
376 drop(save_exclusion);
377 self.schedule_save(None);
378 return Err(error);
379 }
380 };
381 let write_guard = self.begin_write();
382 let save_request_revision = self.save_request_revision();
383 let owner = Arc::new(AsyncMutex::new(ActorStateTransactionOwner {
384 ctx: self.clone(),
385 transaction,
386 save_exclusion: Some(save_exclusion),
387 write_guard: Some(write_guard),
388 finalized: false,
389 committed: false,
390 save_request_revision,
391 statement_count: 0,
392 bind_payload_bytes: 0,
393 rollback_connection_states,
394 }));
395
396 let opened = {
400 let mut slot = self.0.state_transaction.lock();
401 match *slot {
402 StateTransactionSlot::Closed => false,
403 StateTransactionSlot::Idle
404 | StateTransactionSlot::Beginning
405 | StateTransactionSlot::Open(_) => {
406 *slot = StateTransactionSlot::Open(Arc::downgrade(&owner));
407 true
408 }
409 }
410 };
411 let transaction = ActorStateTransaction { owner };
412 if !opened {
413 if let Err(error) = transaction.rollback().await {
414 tracing::debug!(?error, "state transaction rollback at shutdown failed");
415 }
416 return Err(ActorLifecycle::Stopping.build());
417 }
418 Ok(transaction)
419 }
420
421 pub(crate) fn state_transaction_active(&self) -> bool {
423 match *self.0.state_transaction.lock() {
424 StateTransactionSlot::Idle | StateTransactionSlot::Closed => false,
425 StateTransactionSlot::Beginning | StateTransactionSlot::Open(_) => true,
426 }
427 }
428
429 pub(crate) async fn close_state_transactions(&self) {
437 let previous = std::mem::replace(
438 &mut *self.0.state_transaction.lock(),
439 StateTransactionSlot::Closed,
440 );
441 let owner = match previous {
442 StateTransactionSlot::Open(owner) => owner.upgrade(),
443 StateTransactionSlot::Idle
444 | StateTransactionSlot::Beginning
445 | StateTransactionSlot::Closed => None,
446 };
447 let Some(owner) = owner else {
448 return;
449 };
450
451 tracing::warn!(
452 actor_id = %self.actor_id(),
453 "rolling back state transaction left open at shutdown; the action did not finish within the grace period"
454 );
455 if let Err(error) = (ActorStateTransaction { owner }).rollback().await {
456 tracing::debug!(
457 ?error,
458 "state transaction finalized before shutdown rollback"
459 );
460 }
461 }
462
463 fn notify_state_transaction_finished(&self) {
465 if let Some(sender) = self.lifecycle_event_sender() {
466 let _ = sender.send(LifecycleEvent::StateTransactionFinished);
467 }
468 }
469
470 pub fn state(&self) -> Vec<u8> {
471 self.0.current_state.read().clone()
472 }
473
474 pub(crate) async fn persist_state(&self, opts: SaveStateOpts) -> Result<()> {
475 if !self.is_dirty() {
476 return Ok(());
477 }
478
479 let result = if opts.immediate {
480 self.clear_pending_save();
481 self.persist_if_dirty().await
482 } else {
483 let delay = self.compute_save_delay(None);
484 if !delay.is_zero() {
485 sleep(delay).await;
486 }
487 self.persist_if_dirty().await
488 };
489 result?;
490 self.record_state_updated();
491 Ok(())
492 }
493
494 pub fn set_state_initial(&self, state: Vec<u8>) {
497 self.set_initial_state(state);
498 }
499
500 pub fn request_save(&self, opts: RequestSaveOpts) {
508 #[cfg(target_arch = "wasm32")]
509 {
510 self.request_save_best_effort(opts);
511 }
512
513 #[cfg(not(target_arch = "wasm32"))]
514 if let Err(error) = self.request_save_with_revision(opts) {
515 tracing::warn!(?error, "failed to request actor state save");
516 }
517 }
518
519 #[cfg(target_arch = "wasm32")]
520 fn request_save_best_effort(&self, opts: RequestSaveOpts) {
521 let immediate = opts.immediate;
522 let _save_request_revision =
523 self.0.save_request_revision.fetch_add(1, Ordering::SeqCst) + 1;
524 self.notify_request_save_hooks(opts);
525 let already_requested = self.0.save_requested.swap(true, Ordering::SeqCst);
526 let immediate_already_requested = if immediate {
527 self.0.save_requested_immediate.swap(true, Ordering::SeqCst)
528 } else {
529 self.0.save_requested_immediate.load(Ordering::SeqCst)
530 };
531
532 if let Some(max_wait_ms) = opts.max_wait_ms {
533 let deadline = StdInstant::now() + Duration::from_millis(u64::from(max_wait_ms));
534 let mut requested_deadline = self.0.save_requested_within_deadline.lock();
535 *requested_deadline = Some(match *requested_deadline {
536 Some(existing) => existing.min(deadline),
537 None => deadline,
538 });
539 }
540
541 let Some(sender) = self.lifecycle_event_sender() else {
542 return;
543 };
544
545 if opts.max_wait_ms.is_none()
546 && already_requested
547 && (!immediate || immediate_already_requested)
548 {
549 return;
550 }
551
552 let _ = sender.send(LifecycleEvent::SaveRequested { immediate });
553 }
554
555 pub async fn request_save_and_wait(&self, opts: RequestSaveOpts) -> Result<()> {
558 let save_request_revision = self.request_save_with_revision(opts)?;
559 self.wait_for_save_request(save_request_revision).await
560 }
561
562 pub async fn save_state(&self, deltas: Vec<StateDelta>) -> Result<()> {
563 let save_request_revision = self.save_request_revision();
564 self.save_state_with_revision(deltas, save_request_revision)
565 .await
566 }
567
568 pub async fn save_state_and_workflow_batch(
571 &self,
572 workflow_writes: Vec<WorkflowKvWrite>,
573 ) -> Result<()> {
574 let Some(sender) = self.lifecycle_event_sender() else {
575 return Err(ActorRuntime::NotConfigured {
576 component: "lifecycle events".to_owned(),
577 }
578 .build());
579 };
580 let (reply_tx, reply_rx) = oneshot::channel();
581 sender
582 .send(LifecycleEvent::WorkflowFlushRequested {
583 writes: workflow_writes,
584 reply: Reply::from(reply_tx),
585 })
586 .map_err(|_| {
587 ActorRuntime::NotConfigured {
588 component: "lifecycle events".to_owned(),
589 }
590 .build()
591 })?;
592 reply_rx
593 .await
594 .context("receive workflow flush lifecycle reply")?
595 }
596
597
598
599 pub(crate) fn request_save_with_revision(&self, opts: RequestSaveOpts) -> Result<u64> {
600 let immediate = opts.immediate;
601 let save_request_revision = self.0.save_request_revision.fetch_add(1, Ordering::SeqCst) + 1;
602 self.notify_request_save_hooks(opts);
603 let already_requested = self.0.save_requested.swap(true, Ordering::SeqCst);
604 let immediate_already_requested = if immediate {
605 self.0.save_requested_immediate.swap(true, Ordering::SeqCst)
606 } else {
607 self.0.save_requested_immediate.load(Ordering::SeqCst)
608 };
609
610 if let Some(max_wait_ms) = opts.max_wait_ms {
611 let deadline = StdInstant::now() + Duration::from_millis(u64::from(max_wait_ms));
612 let mut requested_deadline = self.0.save_requested_within_deadline.lock();
613 *requested_deadline = Some(match *requested_deadline {
614 Some(existing) => existing.min(deadline),
615 None => deadline,
616 });
617 }
618
619 let Some(sender) = self.lifecycle_event_sender() else {
620 return Err(ActorRuntime::NotConfigured {
621 component: "lifecycle events".to_owned(),
622 }
623 .build());
624 };
625
626 if opts.max_wait_ms.is_none()
627 && already_requested
628 && (!immediate || immediate_already_requested)
629 {
630 return Ok(save_request_revision);
631 }
632
633 sender
634 .send(LifecycleEvent::SaveRequested { immediate })
635 .map(|()| save_request_revision)
636 .map_err(|_| {
637 ActorRuntime::NotConfigured {
638 component: "lifecycle events".to_owned(),
639 }
640 .build()
641 })
642 }
643
644 pub(crate) async fn wait_for_save_request(&self, save_request_revision: u64) -> Result<()> {
645 wait_for_notified_condition(&self.0.save_completion, || {
646 self.0.save_completed_revision.load(Ordering::SeqCst) >= save_request_revision
647 || self.0.save_failed_revision.load(Ordering::SeqCst) >= save_request_revision
648 })
649 .await;
650 if self.0.save_completed_revision.load(Ordering::SeqCst) >= save_request_revision {
651 return Ok(());
652 }
653 Err(ActorLifecycle::Stopping.build())
654 }
655
656 pub(crate) fn throttle_failed_save_request(&self) {
661 self.0
662 .save_requested_immediate
663 .store(false, Ordering::SeqCst);
664 *self.0.last_save_at.lock() = Some(StdInstant::now());
665 }
666
667 pub(crate) fn fail_unpersisted_save_requests(&self) {
671 self.0
672 .save_failed_revision
673 .store(u64::MAX, Ordering::SeqCst);
674 self.0.save_completion.notify_waiters();
675 }
676
677 pub(crate) fn save_requested(&self) -> bool {
678 self.0.save_requested.load(Ordering::SeqCst)
679 }
680
681 pub(crate) fn save_requested_immediate(&self) -> bool {
682 self.0.save_requested_immediate.load(Ordering::SeqCst)
683 }
684
685 pub(crate) fn save_deadline(&self, immediate: bool) -> StdInstant {
686 self.compute_save_deadline(immediate)
687 }
688
689 pub(crate) fn compute_save_deadline(&self, immediate: bool) -> StdInstant {
690 if immediate || self.save_requested_immediate() {
691 return StdInstant::now();
692 }
693
694 let throttled_deadline = StdInstant::now() + self.compute_save_delay(None);
695 let requested_deadline = *self.0.save_requested_within_deadline.lock();
696
697 match requested_deadline {
698 Some(requested_deadline) => throttled_deadline.min(requested_deadline),
699 None => throttled_deadline,
700 }
701 }
702
703 pub(crate) fn save_request_revision(&self) -> u64 {
704 self.0.save_request_revision.load(Ordering::SeqCst)
705 }
706
707 pub(crate) async fn apply_state_deltas(
708 &self,
709 deltas: Vec<StateDelta>,
710 save_request_revision: u64,
711 ) -> Result<()> {
712 self.apply_state_deltas_inner(deltas, None, save_request_revision, None)
713 .await
714 .map(|_| ())
715 }
716
717 pub(super) async fn apply_state_deltas_inner(
718 &self,
719 deltas: Vec<StateDelta>,
720 workflow_writes: Option<Vec<WorkflowKvWrite>>,
721 save_request_revision: u64,
722 expected_state_transaction_epoch: Option<u64>,
723 ) -> Result<bool> {
724 let delta_count = deltas.len();
725 let delta_bytes: usize = deltas.iter().map(StateDelta::payload_len).sum();
726 let workflow_write_count = workflow_writes.as_ref().map_or(0, Vec::len);
727 let current_revision = self.0.state_revision.load(Ordering::SeqCst);
728 tracing::debug!(
729 delta_count,
730 delta_bytes,
731 state_revision = current_revision,
732 save_request_revision,
733 "applying actor state deltas"
734 );
735 self.clear_pending_save();
736
737 if deltas.is_empty() && workflow_write_count == 0 {
738 self.mark_save_request_completed(save_request_revision);
739 self.finish_save_request(save_request_revision);
740 tracing::debug!(
741 delta_count,
742 state_revision = current_revision,
743 save_request_revision,
744 "actor state deltas applied without kv write"
745 );
746 return Ok(true);
747 }
748
749 let save_guard = match expected_state_transaction_epoch {
754 Some(_) => match self.0.save_guard.try_lock() {
755 Ok(save_guard) => save_guard,
756 Err(_) => {
757 tracing::debug!(
758 save_request_revision,
759 "state save guard is held, deferring actor task save"
760 );
761 return Ok(false);
762 }
763 },
764 None => self.0.save_guard.lock().await,
765 };
766 let prepared = {
767 let _save_guard = save_guard;
768 if expected_state_transaction_epoch.is_some_and(|expected| {
769 self.0.state_transaction_epoch.load(Ordering::SeqCst) != expected
770 }) {
771 None
772 } else {
773 let revision = self.0.state_revision.load(Ordering::SeqCst);
774 let mut persisted = self.persisted();
775 let mut next_state = None;
776 let mut actor_to_persist = None;
777 let mut connections_to_persist: Vec<PersistedConnection> = Vec::new();
778 let mut connections_to_delete = Vec::new();
779
780 for delta in deltas {
781 match delta {
782 StateDelta::ActorState(bytes) => {
783 next_state = Some(bytes.clone());
784 persisted.state = bytes.into();
785 }
786 StateDelta::ConnHibernation { conn: _, bytes } => {
787 connections_to_persist.push(
788 decode_persisted_connection(&bytes)
789 .context("decode hibernatable connection state delta")?,
790 );
791 }
792 StateDelta::ConnHibernationRemoved(conn) => {
793 connections_to_delete.push(conn);
794 }
795 }
796 }
797
798 if next_state.is_some() {
799 actor_to_persist = Some(persisted.clone());
800 }
801
802 Some((
803 next_state,
804 actor_to_persist,
805 connections_to_persist,
806 connections_to_delete,
807 revision,
808 self.begin_write(),
809 ))
810 }
811 };
812 let Some((
813 next_state,
814 actor_to_persist,
815 connections_to_persist,
816 connections_to_delete,
817 revision,
818 _write_guard,
819 )) = prepared
820 else {
821 tracing::debug!(
822 save_request_revision,
823 "discarding state serialized across a state transaction boundary"
824 );
825 return Ok(false);
826 };
827
828 if let Some(workflow_writes) = workflow_writes.as_deref() {
829 internal_storage::persist_actor_core_connections_and_workflow(
830 self.sql(),
831 actor_to_persist.as_ref(),
832 &connections_to_persist,
833 &connections_to_delete,
834 workflow_writes,
835 )
836 .await
837 .context("atomically persist actor state, connection deltas, and workflow kv")?;
838 } else if actor_to_persist.is_some()
839 || !connections_to_persist.is_empty()
840 || !connections_to_delete.is_empty()
841 {
842 internal_storage::persist_actor_core_and_connections(
843 self.sql(),
844 actor_to_persist.as_ref(),
845 &connections_to_persist,
846 &connections_to_delete,
847 )
848 .await
849 .context("persist actor state and connection deltas to sqlite")?;
850 }
851
852 if let Some(state) = next_state {
853 self.0.persisted.write().state = state.clone().into();
854 *self.0.current_state.write() = state;
855 }
856 for connection in &connections_to_persist {
857 if let Some(handle) = self.connection(&connection.id) {
858 handle.set_state_initial(connection.state.clone().to_vec());
859 }
860 }
861
862 *self.0.last_save_at.lock() = Some(StdInstant::now());
863
864 if self.0.state_revision.load(Ordering::SeqCst) == revision {
865 self.0.state_dirty.store(false, Ordering::SeqCst);
866 }
867
868 self.mark_save_request_completed(save_request_revision);
869 self.finish_save_request(save_request_revision);
870 tracing::debug!(
871 delta_count,
872 delta_bytes,
873 workflow_write_count,
874 state_revision = self.0.state_revision.load(Ordering::SeqCst),
875 save_request_revision,
876 "actor state deltas applied"
877 );
878 Ok(true)
879 }
880 async fn commit_state_transaction(
881 &self,
882 transaction: &SqliteTransaction,
883 deltas: Vec<StateDelta>,
884 save_request_revision: u64,
885 statement_count: usize,
886 bind_payload_bytes: usize,
887 ) -> Result<()> {
888 let (deltas, pending_hibernation_changes) = match self.prepare_state_deltas(deltas) {
889 Ok(prepared) => prepared,
890 Err(error) => {
891 let _ = transaction.rollback().await;
892 return Err(error);
893 }
894 };
895 let commit_result = async {
896 let revision = self.0.state_revision.load(Ordering::SeqCst);
897 let mut persisted = self.persisted();
898 let mut next_state = None;
899 let mut actor_to_persist = None;
900 let mut connections_to_persist: Vec<PersistedConnection> = Vec::new();
901 let mut connections_to_delete = Vec::new();
902
903 for delta in deltas {
904 match delta {
905 StateDelta::ActorState(bytes) => {
906 next_state = Some(bytes.clone());
907 persisted.state = bytes.into();
908 }
909 StateDelta::ConnHibernation { conn: _, bytes } => {
910 connections_to_persist.push(
911 decode_persisted_connection(&bytes)
912 .context("decode hibernatable connection state delta")?,
913 );
914 }
915 StateDelta::ConnHibernationRemoved(conn) => {
916 connections_to_delete.push(conn);
917 }
918 }
919 }
920
921 if next_state.is_some() {
922 actor_to_persist = Some(persisted);
923 }
924 let statements = internal_storage::build_actor_core_and_connection_statements(
925 actor_to_persist.as_ref(),
926 &connections_to_persist,
927 &connections_to_delete,
928 )?;
929 internal_storage::validate_atomic_state_transaction_budget(
930 statement_count.saturating_add(statements.len()),
931 bind_payload_bytes
932 .saturating_add(internal_storage::statement_bind_payload_len(&statements)),
933 )?;
934 for statement in statements {
935 transaction
936 .execute(statement.sql, statement.params)
937 .await
938 .context("persist actor state inside sqlite transaction")?;
939 }
940 transaction
941 .commit()
942 .await
943 .context("commit sqlite transaction with actor state")?;
944
945 if let Some(state) = next_state {
946 self.0.persisted.write().state = state.clone().into();
947 *self.0.current_state.write() = state;
948 }
949 for connection in &connections_to_persist {
950 if let Some(handle) = self.connection(&connection.id) {
951 handle.set_state_initial(connection.state.clone().to_vec());
952 }
953 }
954 *self.0.last_save_at.lock() = Some(StdInstant::now());
955 if self.0.state_revision.load(Ordering::SeqCst) == revision {
956 self.0.state_dirty.store(false, Ordering::SeqCst);
957 }
958 self.mark_save_request_completed(save_request_revision);
959 self.finish_save_request(save_request_revision);
960 self.record_state_updated();
961 Ok(())
962 }
963 .await;
964
965 if let Err(error) = commit_result {
966 self.restore_pending_hibernation_changes(pending_hibernation_changes);
967 let _ = transaction.rollback().await;
968 return Err(error);
969 }
970 Ok(())
971 }
972
973 pub(crate) async fn wait_for_pending_writes(&self) {
974 loop {
975 if let Some(handle) = self.take_tracked_persist() {
976 let _ = handle.await;
977 continue;
978 }
979
980 let save_guard = self.0.save_guard.lock().await;
981 if self.has_tracked_persist() {
982 drop(save_guard);
983 continue;
984 }
985
986 if self.0.in_flight_state_writes.load(Ordering::SeqCst) == 0 {
987 return;
988 }
989 drop(save_guard);
990
991 self.wait_for_in_flight_writes().await;
992 }
993 }
994
995 pub(crate) async fn wait_for_pending_state_writes(&self) {
996 self.wait_for_pending_writes().await;
997 }
998
999 pub fn begin_on_state_change(&self) -> OnStateChangeGuard {
1000 OnStateChangeGuard::new(self.clone())
1001 }
1002
1003 pub fn on_state_change_started(&self) {
1004 self.0
1005 .on_state_change_in_flight
1006 .fetch_add(1, Ordering::SeqCst);
1007 self.0.sleep.work.keep_awake.increment();
1008 self.reset_sleep_timer();
1009 }
1010
1011 pub fn on_state_change_finished(&self) {
1012 let previous = self.0.on_state_change_in_flight.try_update(
1013 Ordering::SeqCst,
1014 Ordering::SeqCst,
1015 |count| count.checked_sub(1),
1016 );
1017
1018 match previous {
1019 Ok(1) => {
1020 self.0.sleep.work.keep_awake.decrement();
1021 self.0.on_state_change_idle.notify_waiters();
1022 self.reset_sleep_timer();
1023 }
1024 Ok(_) => {
1025 self.0.sleep.work.keep_awake.decrement();
1026 self.reset_sleep_timer();
1027 }
1028 Err(_) => {
1029 tracing::warn!(
1030 actor_id = %self.actor_id(),
1031 "on_state_change finished without a matching start"
1032 );
1033 }
1034 }
1035 }
1036
1037
1038
1039 pub fn persisted(&self) -> PersistedActor {
1040 self.0.persisted.read().clone()
1041 }
1042
1043 pub fn load_persisted(&self, persisted: PersistedActor) {
1044 let state = persisted.state.clone();
1045 *self.0.persisted.write() = persisted;
1046 *self.0.current_state.write() = state.to_vec();
1047 self.0.state_dirty.store(false, Ordering::SeqCst);
1048 self.finish_save_request(self.save_request_revision());
1049 self.0
1050 .metrics
1051 .inc_state_mutation(StateMutationReason::InternalReplace);
1052 }
1053
1054 pub(crate) fn load_last_pushed_alarm(&self, alarm_ts: Option<i64>) {
1055 *self.0.last_pushed_alarm.write() = alarm_ts;
1056 }
1057
1058 pub(crate) fn last_pushed_alarm(&self) -> Option<i64> {
1059 *self.0.last_pushed_alarm.read()
1060 }
1061
1062 pub(crate) async fn persist_last_pushed_alarm(&self, alarm_ts: Option<i64>) -> Result<()> {
1065 internal_storage::persist_last_pushed_alarm(self.sql(), alarm_ts)
1066 .await
1067 .context("persist last pushed alarm to sqlite")
1068 }
1069
1070 pub(crate) fn load_run_wake_at(&self, wake_at: Option<i64>) {
1071 *self.0.run_wake_at.write() = wake_at;
1072 }
1073
1074 pub(crate) fn run_wake_at(&self) -> Option<i64> {
1075 *self.0.run_wake_at.read()
1076 }
1077
1078 pub(crate) async fn persist_run_wake_at(&self, wake_at: Option<i64>) -> Result<()> {
1079 internal_storage::persist_run_wake_at(self.sql(), wake_at)
1080 .await
1081 .context("persist run wake deadline to sqlite")?;
1082 self.load_run_wake_at(wake_at);
1083 Ok(())
1084 }
1085
1086 pub(crate) fn set_initial_state(&self, state: Vec<u8>) {
1087 *self.0.current_state.write() = state.clone();
1088 self.0.persisted.write().state = state.into();
1089 self.0.state_dirty.store(true, Ordering::SeqCst);
1090 self.0.state_revision.fetch_add(1, Ordering::SeqCst);
1091 }
1092
1093 pub fn scheduled_events(&self) -> Vec<PersistedScheduleEvent> {
1094 self.0.persisted.read().scheduled_events.clone()
1095 }
1096
1097 pub fn set_scheduled_events(&self, scheduled_events: Vec<PersistedScheduleEvent>) {
1098 self.0.persisted.write().scheduled_events = scheduled_events;
1099 self.0
1100 .metrics
1101 .inc_state_mutation(StateMutationReason::ScheduledEventsUpdate);
1102 self.mark_dirty();
1103 self.schedule_save(None);
1104 }
1105
1106 pub fn set_input(&self, input: Option<Vec<u8>>) {
1107 self.0.persisted.write().input = input.map(Into::into);
1108 self.0
1109 .metrics
1110 .inc_state_mutation(StateMutationReason::InputSet);
1111 self.mark_dirty();
1112 self.schedule_save(None);
1113 }
1114
1115 pub fn input(&self) -> Option<Vec<u8>> {
1116 self.0.persisted.read().input.clone().map(Into::into)
1117 }
1118
1119 pub fn set_has_initialized(&self, has_initialized: bool) {
1120 {
1121 let mut persisted = self.0.persisted.write();
1122 if persisted.has_initialized == has_initialized {
1123 return;
1124 }
1125 persisted.has_initialized = has_initialized;
1126 }
1127 self.0
1128 .metrics
1129 .inc_state_mutation(StateMutationReason::HasInitialized);
1130 self.mark_dirty();
1131 self.schedule_save(None);
1132 }
1133
1134 pub fn has_initialized(&self) -> bool {
1135 self.0.persisted.read().has_initialized
1136 }
1137
1138 pub fn flush_on_shutdown(&self) {
1139 self.persist_now_tracked("shutdown_flush");
1140 }
1141
1142 pub fn on_request_save(&self, hook: Box<dyn Fn(RequestSaveOpts) + Send + Sync>) {
1143 self.0.request_save_hooks.write().push(Arc::from(hook));
1144 }
1145
1146 fn is_dirty(&self) -> bool {
1147 self.0.state_dirty.load(Ordering::SeqCst)
1148 }
1149
1150 fn mark_dirty(&self) {
1151 self.0.state_dirty.store(true, Ordering::SeqCst);
1152 self.0.state_revision.fetch_add(1, Ordering::SeqCst);
1153 }
1154
1155 fn lifecycle_event_sender(&self) -> Option<mpsc::UnboundedSender<LifecycleEvent>> {
1156 self.0.lifecycle_events.read().clone()
1157 }
1158
1159 fn compute_save_delay(&self, max_wait: Option<Duration>) -> Duration {
1160 let elapsed = self
1161 .0
1162 .last_save_at
1163 .lock()
1164 .map(|instant| instant.elapsed())
1165 .unwrap_or_default();
1166
1167 throttled_save_delay(self.0.state_save_interval, elapsed, max_wait)
1168 }
1169
1170 fn schedule_save(&self, max_wait: Option<Duration>) {
1171 if !self.is_dirty() {
1172 return;
1173 }
1174
1175 let delay = self.compute_save_delay(max_wait);
1176 let scheduled_at = StdInstant::now() + delay;
1177
1178 let mut pending_save = self.0.pending_save.lock();
1179
1180 if let Some(existing) = pending_save.as_ref() {
1181 if existing.scheduled_at <= scheduled_at {
1182 return;
1183 }
1184
1185 existing.handle.abort();
1186 }
1187
1188 let state = self.clone();
1189 let task = async move {
1193 if !delay.is_zero() {
1194 sleep(delay).await;
1195 }
1196
1197 state.take_pending_save();
1198
1199 if let Err(error) = state.persist_if_dirty().await {
1200 tracing::error!(?error, "failed to persist actor state");
1201 }
1202 }
1203 .in_current_span();
1204
1205 #[cfg(not(feature = "wasm-runtime"))]
1206 let handle = {
1207 let Ok(tokio_handle) = Handle::try_current() else {
1208 return;
1209 };
1210 tokio_handle.spawn(task)
1211 };
1212
1213 #[cfg(feature = "wasm-runtime")]
1214 let handle = RuntimeSpawner::spawn(task);
1215
1216 *pending_save = Some(PendingSave {
1217 scheduled_at,
1218 handle,
1219 });
1220 }
1221
1222 fn clear_pending_save(&self) {
1223 if let Some(pending_save) = self.take_pending_save() {
1224 pending_save.handle.abort();
1225 }
1226 }
1227
1228 pub(crate) fn persist_now_tracked(&self, description: &'static str) {
1229 self.clear_pending_save();
1230
1231 let state = self.clone();
1232 let mut tracked_persist = self.0.tracked_persist.lock();
1233 let previous = tracked_persist.take();
1234 let task = async move {
1235 if let Some(previous) = previous {
1236 let _ = previous.await;
1237 }
1238
1239 if let Err(error) = state.persist_state(SaveStateOpts { immediate: true }).await {
1240 tracing::error!(?error, description, "failed to persist actor state");
1241 }
1242 }
1243 .in_current_span();
1244
1245 #[cfg(not(feature = "wasm-runtime"))]
1246 let handle = {
1247 let Ok(tokio_handle) = Handle::try_current() else {
1248 tracing::warn!(
1249 description,
1250 "skipping tracked actor state persistence without runtime"
1251 );
1252 return;
1253 };
1254 tokio_handle.spawn(task)
1255 };
1256
1257 #[cfg(feature = "wasm-runtime")]
1258 let handle = RuntimeSpawner::spawn(task);
1259
1260 *tracked_persist = Some(handle);
1261 }
1262
1263 fn take_pending_save(&self) -> Option<PendingSave> {
1264 self.0.pending_save.lock().take()
1265 }
1266
1267 fn take_tracked_persist(&self) -> Option<JoinHandle<()>> {
1268 self.0.tracked_persist.lock().take()
1269 }
1270
1271 fn has_tracked_persist(&self) -> bool {
1272 self.0.tracked_persist.lock().is_some()
1273 }
1274
1275
1276
1277 async fn persist_if_dirty(&self) -> Result<()> {
1278 if !self.is_dirty() {
1279 return Ok(());
1280 }
1281
1282 let (revision, actor_to_persist, _write_guard) = {
1283 let _save_guard = self.0.save_guard.lock().await;
1284 if !self.is_dirty() {
1285 return Ok(());
1286 }
1287
1288 let revision = self.0.state_revision.load(Ordering::SeqCst);
1289 let persisted = self.persisted();
1290 (revision, persisted, self.begin_write())
1291 };
1292
1293 internal_storage::persist_actor_snapshot(self.sql(), &actor_to_persist)
1294 .await
1295 .context("persist actor state to sqlite")?;
1296
1297 *self.0.last_save_at.lock() = Some(StdInstant::now());
1298
1299 if self.0.state_revision.load(Ordering::SeqCst) == revision {
1300 self.0.state_dirty.store(false, Ordering::SeqCst);
1301 }
1302
1303 Ok(())
1304 }
1305
1306 pub(crate) fn run_startup_commit(
1311 &self,
1312 commit: impl std::future::Future<Output = Result<()>> + Send + 'static,
1313 ) {
1314 self.0
1315 .startup_commit
1316 .send_modify(|startup| startup.pending += 1);
1317 let write = self.begin_write();
1318 let ctx = self.clone();
1319 RuntimeSpawner::spawn(
1320 async move {
1321 let result = commit.await;
1322 drop(write);
1323 let failed = result.is_err();
1324 let mut returned = false;
1325 ctx.0.startup_commit.send_modify(|startup| {
1326 startup.pending = startup.pending.saturating_sub(1);
1327 startup.failed |= failed;
1328 returned = startup.returned;
1329 });
1330 if let Err(error) = result {
1331 tracing::error!(
1332 ?error,
1333 actor_id = %ctx.actor_id(),
1334 "actor startup state commit failed; stopping the actor"
1335 );
1336 if !returned {
1338 return;
1339 }
1340 if let Err(stop_error) = ctx.stop_with_error(format!("{error:#}")) {
1341 tracing::error!(
1342 ?stop_error,
1343 actor_id = %ctx.actor_id(),
1344 "failed to report the startup commit failure to the engine"
1345 );
1346 }
1347 }
1348 }
1349 .in_current_span(),
1350 );
1351 }
1352
1353 pub(crate) fn begin_startup_commits(&self) {
1356 self.0.startup_commit.send_modify(|startup| {
1357 startup.returned = false;
1358 startup.failed = false;
1359 });
1360 }
1361
1362 pub(crate) fn finish_startup_commits(&self) -> Result<()> {
1365 let mut failed = false;
1366 self.0.startup_commit.send_modify(|startup| {
1367 startup.returned = true;
1368 failed = startup.failed;
1369 });
1370 if failed {
1371 return Err(ActorLifecycle::Stopping.build())
1372 .context("actor startup state commit failed");
1373 }
1374 Ok(())
1375 }
1376
1377 pub(crate) async fn wait_startup_commit(&self) -> Result<()> {
1381 let mut startup = self.0.startup_commit.subscribe();
1382 let failed = startup
1383 .wait_for(|startup| startup.pending == 0)
1384 .await
1385 .map(|startup| startup.failed);
1386 match failed {
1387 Ok(false) => Ok(()),
1388 Ok(true) | Err(_) => {
1389 Err(ActorLifecycle::Stopping.build()).context("actor startup state commit failed")
1390 }
1391 }
1392 }
1393
1394 fn begin_write(&self) -> InFlightWrite {
1395 self.0.in_flight_state_writes.fetch_add(1, Ordering::SeqCst);
1396 InFlightWrite { ctx: self.clone() }
1397 }
1398
1399 async fn wait_for_in_flight_writes(&self) {
1400 wait_for_notified_condition(&self.0.state_write_completion, || {
1401 self.0.in_flight_state_writes.load(Ordering::SeqCst) == 0
1402 })
1403 .await;
1404 }
1405
1406 fn finish_save_request(&self, save_request_revision: u64) {
1407 if self.0.save_request_revision.load(Ordering::SeqCst) == save_request_revision {
1408 self.0.save_requested.store(false, Ordering::SeqCst);
1409 self.0
1410 .save_requested_immediate
1411 .store(false, Ordering::SeqCst);
1412 *self.0.save_requested_within_deadline.lock() = None;
1413 }
1414 }
1415
1416 fn mark_save_request_completed(&self, save_request_revision: u64) {
1417 self.0
1418 .save_completed_revision
1419 .fetch_max(save_request_revision, Ordering::SeqCst);
1420 self.0.save_completion.notify_waiters();
1421 }
1422
1423 fn notify_request_save_hooks(&self, opts: RequestSaveOpts) {
1424 let hooks = self.0.request_save_hooks.read().clone();
1425 for hook in hooks {
1426 hook(opts);
1427 }
1428 }
1429}
1430
1431struct InFlightWrite {
1432 ctx: ActorContext,
1433}
1434
1435impl Drop for InFlightWrite {
1436 fn drop(&mut self) {
1437 if self
1438 .ctx
1439 .0
1440 .in_flight_state_writes
1441 .fetch_sub(1, Ordering::SeqCst)
1442 == 1
1443 {
1444 self.ctx.0.state_write_completion.notify_waiters();
1445 }
1446 }
1447}
1448
1449async fn wait_for_notified_condition(notify: &Notify, mut ready: impl FnMut() -> bool) {
1457 loop {
1458 let notified = notify.notified();
1459 tokio::pin!(notified);
1460 notified.as_mut().enable();
1461
1462 if ready() {
1463 return;
1464 }
1465
1466 notified.await;
1467 }
1468}
1469
1470fn throttled_save_delay(
1471 save_interval: Duration,
1472 time_since_last_save: Duration,
1473 max_wait: Option<Duration>,
1474) -> Duration {
1475 let save_delay = save_interval.saturating_sub(time_since_last_save);
1476 if let Some(max_wait) = max_wait {
1477 save_delay.min(max_wait)
1478 } else {
1479 save_delay
1480 }
1481}
1482
1483