@@ -1121,12 +1121,44 @@ impl ProcedureContext {
11211121 self . timestamp = new_time;
11221122 }
11231123
1124+ /// Acquire a mutable transaction
1125+ /// and execute `body` with read-write access to the database.
1126+ ///
1127+ /// When a panic occurs,
1128+ /// the transaction will be rolled back and its mutations discarded.
1129+ /// Otherwise, the transaction will be committed and its mutations persisted.
1130+ ///
1131+ /// Regardless of the transaction's success or failure,
1132+ /// the return value of `body` is not persisted to the commitlog
1133+ /// or broadcast to subscribed clients.
1134+ /// Clients attribute mutations performed by this transaction to `Event::UnknownTransaction`.
1135+ ///
1136+ /// If the transaction fails to commit after `body` returns,
1137+ /// e.g., due to a conflict with a concurrent transaction,
1138+ /// this method will re-invoke `body` with a new transaction in order to retry.
1139+ /// The transaction will be retried at most once.
1140+ /// If it fails to commit a second time, this method will panic.
1141+ ///
1142+ /// Because `body` may be run multiple times,
1143+ /// and is expected to perform the same set of database operations
1144+ /// and return the same result on each invocation,
1145+ /// callers should avoid writing to any captured mutable state within `body`,
1146+ /// This includes interior mutability through types like [`std::cell::Cell`].
1147+ #[ cfg( feature = "unstable" ) ]
1148+ pub fn with_tx < T > ( & mut self , body : impl Fn ( & TxContext ) -> T ) -> T {
1149+ use core:: convert:: Infallible ;
1150+ match self . try_with_tx :: < T , Infallible > ( |tx| Ok ( body ( tx) ) ) {
1151+ Ok ( v) => v,
1152+ Err ( e) => match e { } ,
1153+ }
1154+ }
1155+
11241156 /// Acquire a mutable transaction
11251157 /// and execute `body` with read-write access to the database.
11261158 ///
11271159 /// When `body().is_ok()`,
11281160 /// the transaction will be committed and its mutations persisted.
1129- /// When `!body().is_ok()`,
1161+ /// When `!body().is_ok()` or a panic occurs in `body` ,
11301162 /// the transaction will be rolled back and its mutations discarded.
11311163 ///
11321164 /// Regardless of the transaction's success or failure,
@@ -1146,43 +1178,57 @@ impl ProcedureContext {
11461178 /// callers should avoid writing to any captured mutable state within `body`,
11471179 /// This includes interior mutability through types like [`std::cell::Cell`].
11481180 #[ cfg( feature = "unstable" ) ]
1149- pub fn with_tx < R : IsOk > ( & mut self , body : impl Fn ( & TxContext ) -> R ) -> R {
1181+ pub fn try_with_tx < T , E > ( & mut self , body : impl Fn ( & TxContext ) -> Result < T , E > ) -> Result < T , E > {
1182+ let abort = || {
1183+ sys:: procedure:: procedure_abort_mut_tx ( )
1184+ . expect ( "should have a pending mutable anon tx as `procedure_start_mut_tx` preceded" )
1185+ } ;
1186+
11501187 let run = || {
11511188 // Start the transaction.
11521189 let timestamp = sys:: procedure:: procedure_start_mut_tx ( ) . expect (
11531190 "holding `&mut ProcedureContext`, so should not be in a tx already; called manually elsewhere?" ,
11541191 ) ;
11551192 let timestamp = Timestamp :: from_micros_since_unix_epoch ( timestamp) ;
11561193
1157- // We've resumed, so do the work.
1194+ // We've resumed, so let's do the work, but first prepare the context .
11581195 let tx = ReducerContext :: new ( Local { } , self . sender , self . connection_id , timestamp) ;
11591196 let tx = TxContext ( tx) ;
1160- body ( & tx)
1197+
1198+ // Guard the execution of `body` with a scope-guard that `abort`s on panic.
1199+ // Wasmtime now supports unwinding, so we need to protect against that.
1200+ // We're not using `scopeguard::guard` here to avoid an extra dependency.
1201+ struct DoOnDrop < F : Fn ( ) > ( F ) ;
1202+ impl < F : Fn ( ) > Drop for DoOnDrop < F > {
1203+ fn drop ( & mut self ) {
1204+ ( self . 0 ) ( ) ;
1205+ }
1206+ }
1207+ let abort_guard = DoOnDrop ( abort) ;
1208+ let res = body ( & tx) ;
1209+ // Defuse the bomb.
1210+ let DoOnDrop ( _) = abort_guard;
1211+ res
11611212 } ;
11621213
11631214 let mut res = run ( ) ;
1164- let abort = || {
1165- sys:: procedure:: procedure_abort_mut_tx ( )
1166- . expect ( "should have a pending mutable anon tx as `procedure_start_mut_tx` preceded" )
1167- } ;
11681215
11691216 // Commit or roll back?
1170- if res. is_ok ( ) {
1171- if sys:: procedure:: procedure_commit_mut_tx ( ) . is_err ( ) {
1217+ match res {
1218+ Ok ( _) if sys:: procedure:: procedure_commit_mut_tx ( ) . is_err ( ) => {
1219+ // Tried to commit, but couldn't. Retry once.
11721220 log:: warn!( "committing anonymous transaction failed" ) ;
1173-
11741221 // NOTE(procedure,centril): there's no actual guarantee that `body`
11751222 // does the exact same as the time before, as the timestamps differ
11761223 // and due to interior mutability.
11771224 res = run ( ) ;
1178- if res. is_ok ( ) {
1179- sys:: procedure:: procedure_commit_mut_tx ( ) . expect ( "transaction retry failed again" )
1180- } else {
1181- abort ( ) ;
1225+ match res {
1226+ Ok ( _) => sys:: procedure:: procedure_commit_mut_tx ( ) . expect ( "transaction retry failed again" ) ,
1227+ Err ( _) => abort ( ) ,
11821228 }
11831229 }
1184- } else {
1185- abort ( ) ;
1230+ Ok ( _ ) => { }
1231+ Err ( _ ) => abort ( ) ,
11861232 }
11871233
11881234 res
0 commit comments