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