mj_controller/controller/checkpoint/
capture.rs1use super::*;
2
3impl Controller {
4 pub(super) async fn checkpoint_session_controlled_with_manager(
5 &mut self,
6 session_id: &str,
7 executor: &(impl CommandExecutor + Sync),
8 manager: Option<&SessionManagerControl>,
9 ) -> Result<CheckpointMetadata> {
10 let previous = self
11 .state
12 .sessions
13 .get(session_id)
14 .with_context(|| format!("unknown session {session_id}"))?
15 .clone();
16 previous.validate_configuration(&self.config)?;
17 ensure!(
18 !matches!(
19 previous.state,
20 SessionState::Closing | SessionState::Destroying
21 ),
22 "session {session_id} is already closing; resume that close instead of starting an ordinary checkpoint"
23 );
24 let record = self.state.sessions.get_mut(session_id).unwrap();
25 record.state = SessionState::Checkpointing;
26 record.updated_at = now();
27 record.last_checkpoint_error = None;
28 self.persist_session_transition_or_restore(
29 session_id,
30 &previous,
31 "persist checkpointing state before creating a checkpoint",
32 )?;
33
34 match self
35 .checkpoint_session_latched(
36 session_id,
37 executor,
38 manager,
39 LatchExclusivity::ReleaseAfterLatch,
40 CheckpointExportPolicy::Always,
41 )
42 .await
43 {
44 Ok(latched) => {
45 let artifact = latched.artifact.clone();
46 if let Err(error) = mj_core::test_hooks::reach_test_hook(
47 "checkpoint_archive_before_database_publication",
48 ) {
49 latched.abandon(session_id).await;
50 return Err(remove_uninstalled_checkpoint(
51 &artifact.metadata.archive_path,
52 error,
53 ));
54 }
55 {
56 let record = self.state.sessions.get_mut(session_id).unwrap();
57 record.state = SessionState::Running;
58 record.native_session_id = Some(artifact.native_session_id.clone());
59 record.checkpoint = Some(artifact.metadata.clone());
60 record.updated_at = now();
61 record.last_error = None;
62 record.last_checkpoint_error = None;
63 }
64 let persist_started = Instant::now();
65 if let Err(error) = self.persist_checkpoint_transition_or_restore(
66 session_id,
67 &previous,
68 "persist verified checkpoint before releasing relay history",
69 ) {
70 latched.abandon(session_id).await;
71 return Err(error);
72 }
73 tracing::info!(
74 session_id,
75 persist_ms = persist_started.elapsed().as_millis() as u64,
76 "checkpoint metadata persisted"
77 );
78 prune_replaced_checkpoint(previous.checkpoint.as_ref(), &artifact.metadata);
79 release_projection_behind_checkpoint(session_id, &artifact.metadata);
80 if let Err(error) = latched.complete().await {
81 tracing::warn!(
87 session_id,
88 "verified checkpoint was saved, but the relay could not be told to release the history it covers: {error:#}"
89 );
90 }
91 Ok(artifact.metadata)
92 }
93 Err(error) => {
94 let deferred = checkpoint_was_deferred(&error);
99 if let Some(record) = self.state.sessions.get_mut(session_id) {
100 record.state = if previous.state == SessionState::Checkpointing {
101 SessionState::Running
102 } else {
103 previous.state
104 };
105 record.updated_at = now();
106 if !deferred {
107 record.last_checkpoint_error = Some(format!("{error:#}"));
108 }
109 }
110 Err(self.persist_failed_checkpoint_state_or_restore(session_id, &previous, error))
111 }
112 }
113 }
114
115 pub async fn create_recovery_checkpoint_managed_controlled(
118 &self,
119 session_id: &str,
120 manager: &SessionManagerControl,
121 executor: &(impl CommandExecutor + Sync),
122 ) -> Result<CheckpointArtifact> {
123 self.create_recovery_checkpoint_with_manager(session_id, Some(manager), executor)
124 .await
125 }
126
127 pub(super) async fn create_recovery_checkpoint_with_manager(
128 &self,
129 session_id: &str,
130 manager: Option<&SessionManagerControl>,
131 executor: &(impl CommandExecutor + Sync),
132 ) -> Result<CheckpointArtifact> {
133 let previous_checkpoint = self
134 .state
135 .sessions
136 .get(session_id)
137 .with_context(|| format!("unknown session {session_id}"))?
138 .checkpoint
139 .clone();
140 let latched = self
141 .checkpoint_session_latched_with_recovery_stage(
142 session_id,
143 executor,
144 manager,
145 LatchExclusivity::ReleaseAfterLatch,
146 CheckpointExportPolicy::Always,
147 true,
148 )
149 .await?;
150 let artifact = latched.artifact.clone();
151 let verification = {
152 let _verifying = ProvisionStageGuard::new(executor, ProvisionStage::Verifying);
153 verify_checkpoint_artifact(session_id, &artifact)
154 };
155 if let Err(error) = verification {
156 latched.abandon(session_id).await;
157 return Err(remove_uninstalled_checkpoint(
158 &artifact.metadata.archive_path,
159 error.context("final recovery checkpoint verification"),
160 ));
161 }
162 if let Err(error) =
163 mj_core::test_hooks::reach_test_hook("checkpoint_archive_before_database_publication")
164 {
165 latched.abandon(session_id).await;
166 return Err(remove_uninstalled_checkpoint(
167 &artifact.metadata.archive_path,
168 error,
169 ));
170 }
171 let persist_started = Instant::now();
172 if let Err(error) = crate::database::record_recovery_success(
173 session_id,
174 &artifact.native_session_id,
175 &artifact.metadata,
176 ) {
177 latched.abandon(session_id).await;
178 return Err(error
179 .context("persist verified recovery checkpoint before releasing relay history"));
180 }
181 tracing::info!(
182 session_id,
183 persist_ms = persist_started.elapsed().as_millis() as u64,
184 "recovery checkpoint metadata persisted"
185 );
186 if let Err(error) = latched.complete().await {
187 tracing::warn!(
192 session_id,
193 "recovery checkpoint was saved, but the relay could not be told to release the history it covers: {error:#}"
194 );
195 }
196 prune_replaced_checkpoint(previous_checkpoint.as_ref(), &artifact.metadata);
197 release_projection_behind_checkpoint(session_id, &artifact.metadata);
198 Ok(artifact)
199 }
200}