rullst_orm/pool/
telemetry.rs1use futures::future::BoxFuture;
4
5use super::Orm;
6use super::savepoint::{self, ManagedSavepoint, SharedTransaction};
7use crate::post_commit::PostCommitScope;
8
9impl Orm {
10 #[tracing::instrument(
12 name = "rullst.orm.transaction.begin",
13 target = "rullst_orm",
14 fields(orm.driver = tracing::field::Empty)
15 )]
16 pub async fn begin_transaction() -> Result<crate::db::Transaction<'static>, crate::Error> {
17 Self::record_driver();
18 let pool = Self::pool()?;
19 pool.begin().await.map_err(Into::into)
20 }
21
22 #[tracing::instrument(
43 name = "rullst.orm.transaction",
44 target = "rullst_orm",
45 skip(f),
46 fields(
47 orm.driver = tracing::field::Empty,
48 orm.outcome = tracing::field::Empty
49 )
50 )]
51 pub async fn transaction<F, R, E>(f: F) -> Result<R, crate::Error>
52 where
53 F: FnOnce(
54 SharedTransaction,
55 )
56 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<R, E>> + Send>>
57 + Send,
58 E: std::fmt::Display,
59 {
60 Self::record_driver();
61 crate::__transaction_access::ensure_allowed()?;
62 let nested = match crate::CURRENT_TX.try_with(Clone::clone) {
66 Ok(outer) => ManagedSavepoint::begin(outer.clone())
67 .await?
68 .map(|savepoint| (outer, savepoint)),
69 Err(_) => None,
70 };
71 let (transaction, savepoint) = match nested {
72 Some((outer, savepoint)) => (outer, Some(savepoint)),
73 None => {
74 let tx = Self::begin_transaction().await?;
75 (std::sync::Arc::new(tokio::sync::Mutex::new(Some(tx))), None)
76 }
77 };
78
79 let post_commit = PostCommitScope::new();
80 let closure = savepoint::run_level(transaction.clone(), f(transaction.clone()));
82 let result = match savepoint {
83 Some(_) => post_commit.scope(closure).await,
85 None => {
86 post_commit
87 .scope(crate::CURRENT_TX.scope(transaction.clone(), closure))
88 .await
89 }
90 };
91 let (value, failure) = match result {
92 Ok(value) => (Some(value), None),
93 Err(error) => (None, Some(error.to_string())),
94 };
95 let finished = match savepoint {
96 Some(savepoint) => finish_savepoint(savepoint, post_commit, failure).await,
97 None => finish_transaction(transaction, post_commit, failure).await,
98 };
99 finished?;
100 value.ok_or_else(|| {
101 crate::Error::Internal("managed transaction finished without a value".to_string())
102 })
103 }
104
105 fn record_driver() {
106 if let Ok(driver) = Self::try_driver() {
107 tracing::Span::current().record("orm.driver", driver);
108 }
109 }
110}
111
112fn record_outcome(outcome: &'static str) {
113 tracing::Span::current().record("orm.outcome", outcome);
114}
115
116fn finish_transaction(
118 transaction: SharedTransaction,
119 post_commit: PostCommitScope,
120 failure: Option<String>,
121) -> BoxFuture<'static, Result<(), crate::Error>> {
122 Box::pin(async move {
123 let owned = transaction.lock_owned().await.take();
124 let Some(mut owned) = owned else {
125 let (outcome, action) = if failure.is_some() {
126 ("rollback_ownership_missing", "rollback")
127 } else {
128 ("commit_ownership_missing", "commit")
129 };
130 record_outcome(outcome);
131 return Err(crate::Error::Internal(format!(
132 "managed transaction ownership was removed before automatic {action}"
133 )));
134 };
135 let leaked = match savepoint::unwind_leaked(&mut owned).await {
139 Ok(leaked) => leaked,
140 Err(unwind_error) => {
141 drop(owned);
144 record_outcome("rollback_failed");
145 let failure = failure.map_or_else(String::new, |error| format!("{error}; "));
146 return Err(crate::Error::DatabaseError(format!(
147 "Transaction failed: {failure}rolling back a nested savepoint left open also failed: {unwind_error}",
148 )));
149 }
150 };
151 if let Some(error) = failure {
152 if let Err(rollback_error) = owned.rollback().await {
153 record_outcome("rollback_failed");
154 return Err(crate::Error::DatabaseError(format!(
155 "Transaction failed: {error}; rollback also failed: {rollback_error}",
156 )));
157 }
158 record_outcome("rolled_back");
159 return Err(crate::Error::DatabaseError(format!(
160 "Transaction failed: {error}",
161 )));
162 }
163 if leaked {
164 if let Err(rollback_error) = owned.rollback().await {
165 record_outcome("rollback_failed");
166 return Err(crate::Error::DatabaseError(format!(
167 "a nested savepoint was left open and the transaction rollback failed: {rollback_error}",
168 )));
169 }
170 record_outcome("rolled_back_unbalanced");
171 return Err(crate::Error::Internal(
172 "a nested transaction savepoint was left open, so the managed transaction was rolled back instead of committed"
173 .to_string(),
174 ));
175 }
176 if let Err(error) = owned.commit().await {
177 record_outcome("commit_failed");
178 return Err(error.into());
179 }
180 if let Err(error) = post_commit.commit().await {
181 record_outcome("committed_post_commit_failed");
182 return Err(error);
183 }
184 record_outcome("committed");
185 Ok(())
186 })
187}
188
189fn finish_savepoint(
192 mut savepoint: ManagedSavepoint,
193 post_commit: PostCommitScope,
194 failure: Option<String>,
195) -> BoxFuture<'static, Result<(), crate::Error>> {
196 Box::pin(async move {
197 if let Some(error) = failure {
198 drop(post_commit);
199 if let Err(rollback_error) = savepoint.rollback().await {
200 record_outcome("savepoint_rollback_failed");
201 return Err(crate::Error::DatabaseError(format!(
202 "Transaction failed: {error}; savepoint rollback also failed: {rollback_error}",
203 )));
204 }
205 record_outcome("savepoint_rolled_back");
206 return Err(crate::Error::DatabaseError(format!(
207 "Transaction failed: {error}",
208 )));
209 }
210 if let Err(error) = savepoint.release().await {
211 record_outcome("savepoint_release_failed");
212 return Err(error);
213 }
214 if let Err(error) = post_commit.promote_to_parent().await {
215 record_outcome("savepoint_released_post_commit_failed");
216 return Err(error);
217 }
218 record_outcome("savepoint_released");
219 Ok(())
220 })
221}