vantage_vista/
transaction.rs1mod direct;
2mod joining;
3mod participant;
4mod shell;
5mod slot;
6mod wrap;
7
8use std::any::Any;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
11
12use futures_core::future::BoxFuture;
13use indexmap::{IndexMap, IndexSet};
14use vantage_core::{Result, VantageError, error};
15
16pub use direct::direct;
17pub use participant::{TxKey, TxParticipant};
18pub use slot::{TxBackend, TxOpen, TxSlot};
19
20use joining::Joined;
21
22type Deferred = Box<dyn FnOnce() -> BoxFuture<'static, Result<()>> + Send>;
23
24const COMMITTED: &str = "committed";
27
28#[derive(Debug)]
30#[non_exhaustive]
31pub struct Committed {
32 pub written: Vec<String>,
34 pub unprotected: Vec<String>,
38}
39
40#[derive(Default)]
41struct State {
42 typed: IndexMap<TxKey, Arc<dyn Any + Send + Sync>>,
43 ordered: Vec<(TxKey, Arc<dyn TxParticipant>)>,
44 written: IndexSet<String>,
45 tables_of: IndexMap<TxKey, IndexSet<String>>,
48 unprotected: IndexSet<String>,
50 deferred: Vec<Deferred>,
51}
52
53struct Taken {
55 participants: Vec<(TxKey, Arc<dyn TxParticipant>)>,
56 deferred: Vec<Deferred>,
57 written: IndexSet<String>,
58 tables_of: IndexMap<TxKey, IndexSet<String>>,
59}
60
61#[derive(Default)]
62struct Inner {
63 state: Mutex<State>,
64 finished: AtomicBool,
65}
66
67#[derive(Clone)]
71pub struct Transaction(Arc<Inner>);
72
73#[derive(Clone)]
75pub struct WeakTransaction(Weak<Inner>);
76
77impl Default for Transaction {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl Transaction {
84 pub fn new() -> Transaction {
85 Transaction(Arc::new(Inner::default()))
86 }
87
88 pub fn downgrade(&self) -> WeakTransaction {
89 WeakTransaction(Arc::downgrade(&self.0))
90 }
91
92 pub fn same(&self, other: &Transaction) -> bool {
93 Arc::ptr_eq(&self.0, &other.0)
94 }
95
96 fn state(&self) -> MutexGuard<'_, State> {
97 self.0.state.lock().unwrap_or_else(PoisonError::into_inner)
98 }
99
100 pub fn participant<P: TxParticipant>(
103 &self,
104 key: TxKey,
105 make: impl FnOnce() -> P,
106 ) -> Result<Arc<P>> {
107 let mut state = self.state();
108 if self.is_finished() {
111 return Err(error!("transaction finished"));
112 }
113 let joined = match state.typed.get(&key) {
114 Some(existing) => existing
115 .clone()
116 .downcast::<P>()
117 .map_err(|_| error!("transaction participant under this key is another type"))?,
118 None => {
119 let made = Arc::new(make());
120 state.typed.insert(key, made.clone());
121 state.ordered.push((key, made.clone()));
122 made
123 }
124 };
125 drop(state);
126 joining::note_key(self, key);
129 Ok(joined)
130 }
131
132 pub fn defer(&self, after: Deferred) {
134 self.state().deferred.push(after);
135 }
136
137 pub fn note_written(&self, table: &str) {
139 self.state().written.insert(table.to_string());
140 }
141
142 fn note_written_by(&self, table: &str, joined: &Joined) {
144 let mut state = self.state();
145 if joined.direct {
146 state.unprotected.insert(table.to_string());
147 return;
148 }
149 state.written.insert(table.to_string());
150 for key in &joined.keys {
151 state
152 .tables_of
153 .entry(*key)
154 .or_default()
155 .insert(table.to_string());
156 }
157 }
158
159 pub fn unprotected(&self) -> Vec<String> {
162 self.state().unprotected.iter().cloned().collect()
163 }
164
165 pub fn committed_before_failure(err: &VantageError) -> Vec<String> {
169 err.context
170 .get(COMMITTED)
171 .and_then(|tables| serde_json::from_str(tables).ok())
172 .unwrap_or_default()
173 }
174
175 pub fn is_finished(&self) -> bool {
176 self.0.finished.load(Ordering::SeqCst)
177 }
178
179 pub fn check_open(&self) -> Result<()> {
180 if self.is_finished() {
181 return Err(error!("transaction finished"));
182 }
183 Ok(())
184 }
185
186 pub async fn commit(&self) -> Result<Committed> {
193 let Taken {
194 participants,
195 deferred,
196 written,
197 tables_of,
198 } = self.finish()?;
199
200 for (done, (_, participant)) in participants.iter().enumerate() {
201 if let Err(mut err) = participant.commit().await {
202 Self::roll_back_all(&participants[done + 1..]).await;
203 let landed: IndexSet<&String> = participants[..done]
204 .iter()
205 .filter_map(|(key, _)| tables_of.get(key))
206 .flatten()
207 .collect();
208 if !landed.is_empty() {
209 let landed: Vec<&String> = landed.into_iter().collect();
210 let landed = serde_json::to_string(&landed).unwrap_or_default();
211 err.context.insert(COMMITTED.to_string(), landed);
212 }
213 return Err(err);
214 }
215 }
216
217 for after in deferred {
218 if let Err(err) = after().await {
219 tracing::warn!("deferred work after commit failed: {err}");
220 }
221 }
222 Ok(Committed {
223 written: written.into_iter().collect(),
224 unprotected: self.unprotected(),
225 })
226 }
227
228 pub async fn rollback(&self) -> Result<()> {
229 if let Ok(taken) = self.finish() {
231 Self::roll_back_all(&taken.participants).await;
232 }
233 Ok(())
234 }
235
236 fn finish(&self) -> Result<Taken> {
237 let mut state = self.state();
238 if self.0.finished.swap(true, Ordering::SeqCst) {
239 return Err(error!("transaction finished"));
240 }
241 state.typed.clear();
242 Ok(Taken {
243 participants: std::mem::take(&mut state.ordered),
244 deferred: std::mem::take(&mut state.deferred),
245 written: std::mem::take(&mut state.written),
246 tables_of: std::mem::take(&mut state.tables_of),
247 })
248 }
249
250 async fn roll_back_all(participants: &[(TxKey, Arc<dyn TxParticipant>)]) {
251 for (_, participant) in participants {
252 if let Err(err) = participant.rollback().await {
253 tracing::warn!("transaction rollback failed: {err}");
254 }
255 }
256 }
257}
258
259impl WeakTransaction {
260 pub fn upgrade(&self) -> Option<Transaction> {
261 self.0.upgrade().map(Transaction)
262 }
263}
264
265pub fn cant_take_part(table: &str) -> VantageError {
269 error!(format!("\"{table}\" can't take part in a transaction")).mark_unsupported()
270}
271
272pub(crate) fn refuse_rebind() -> VantageError {
274 error!("this shell can't take part in a transaction").mark_unsupported()
275}
276
277pub(crate) fn refuse_lock() -> VantageError {
278 error!("this shell can't lock rows").mark_unsupported()
279}