Skip to content

Commit 0a32517

Browse files
authored
Add ProcedureContext::with_tx (#3638)
# Description of Changes Adds `ProcedureContext::{with_tx, try_with_tx}`. Fixes #3515. # API and ABI breaking changes None # Expected complexity level and risk 2 # Testing An integration test `test_calling_with_tx` is added.
1 parent 3395b1e commit 0a32517

File tree

35 files changed

+1333
-243
lines changed

35 files changed

+1333
-243
lines changed

crates/bindings-sys/src/lib.rs

Lines changed: 163 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ pub mod raw {
645645
pub fn get_jwt(connection_id_ptr: *const u8, bytes_source_id: *mut BytesSource) -> u16;
646646
}
647647

648+
#[cfg(feature = "unstable")]
648649
#[link(wasm_import_module = "spacetime_10.3")]
649650
extern "C" {
650651
/// Suspends execution of this WASM instance until approximately `wake_at_micros_since_unix_epoch`.
@@ -661,8 +662,76 @@ pub mod raw {
661662
/// - The calling WASM instance is holding open a transaction.
662663
/// - The calling WASM instance is not executing a procedure.
663664
// TODO(procedure-sleep-until): remove this
664-
#[cfg(feature = "unstable")]
665665
pub fn procedure_sleep_until(wake_at_micros_since_unix_epoch: i64) -> i64;
666+
667+
/// Starts a mutable transaction,
668+
/// suspending execution of this WASM instance until
669+
/// a mutable transaction lock is aquired.
670+
///
671+
/// Upon resuming, returns `0` on success,
672+
/// enabling further calls that require a pending transaction,
673+
/// or an error code otherwise.
674+
///
675+
/// # Traps
676+
///
677+
/// Traps if:
678+
/// - `out` is NULL or `out[..size_of::<i64>()]` is not in bounds of WASM memory.
679+
///
680+
/// # Errors
681+
///
682+
/// Returns an error:
683+
///
684+
/// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
685+
pub fn procedure_start_mut_tx(out: *mut i64) -> u16;
686+
687+
/// Commits a mutable transaction,
688+
/// suspending execution of this WASM instance until
689+
/// the transaction has been committed
690+
/// and subscription queries have been run and broadcast.
691+
///
692+
/// Upon resuming, returns `0` on success, or an error code otherwise.
693+
///
694+
/// # Traps
695+
///
696+
/// This function does not trap.
697+
///
698+
/// # Errors
699+
///
700+
/// Returns an error:
701+
///
702+
/// - `TRANSACTION_NOT_ANONYMOUS`,
703+
/// if the transaction was not started in [`procedure_start_mut_tx`].
704+
/// This can happen if this syscall is erroneously called by a reducer.
705+
/// The code `NOT_IN_TRANSACTION` does not happen,
706+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
707+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
708+
/// This currently does not happen as anonymous read transactions
709+
/// are not exposed to modules.
710+
pub fn procedure_commit_mut_tx() -> u16;
711+
712+
/// Aborts a mutable transaction,
713+
/// suspending execution of this WASM instance until
714+
/// the transaction has been rolled back.
715+
///
716+
/// Upon resuming, returns `0` on success, or an error code otherwise.
717+
///
718+
/// # Traps
719+
///
720+
/// This function does not trap.
721+
///
722+
/// # Errors
723+
///
724+
/// Returns an error:
725+
///
726+
/// - `TRANSACTION_NOT_ANONYMOUS`,
727+
/// if the transaction was not started in [`procedure_start_mut_tx`].
728+
/// This can happen if this syscall is erroneously called by a reducer.
729+
/// The code `NOT_IN_TRANSACTION` does not happen,
730+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
731+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
732+
/// This currently does not happen as anonymous read transactions
733+
/// are not exposed to modules.
734+
pub fn procedure_abort_mut_tx() -> u16;
666735
}
667736

668737
/// What strategy does the database index use?
@@ -819,7 +888,7 @@ impl fmt::Display for Errno {
819888

820889
/// Convert the status value `x` into a result.
821890
/// When `x = 0`, we have a success status.
822-
fn cvt(x: u16) -> Result<(), Errno> {
891+
fn cvt(x: u16) -> Result<()> {
823892
match Errno::from_code(x) {
824893
None => Ok(()),
825894
Some(err) => Err(err),
@@ -838,13 +907,25 @@ fn cvt(x: u16) -> Result<(), Errno> {
838907
/// - The function `f` never reads a safe and valid `T` from the `out` pointer
839908
/// before writing a safe and valid `T` to it.
840909
#[inline]
841-
unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T, Errno> {
910+
unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T> {
842911
let mut out = MaybeUninit::uninit();
843912
let f_code = f(out.as_mut_ptr());
844913
cvt(f_code)?;
845914
Ok(out.assume_init())
846915
}
847916

917+
/// Runs the given function `f`.
918+
///
919+
/// Assuming the call to `f` returns 0, `Ok(())` is returned,
920+
/// and otherwise `Err(err)` is returned.
921+
#[inline]
922+
#[cfg(feature = "unstable")]
923+
fn call_no_ret(f: impl FnOnce() -> u16) -> Result<()> {
924+
let f_code = f();
925+
cvt(f_code)?;
926+
Ok(())
927+
}
928+
848929
/// Queries the `table_id` associated with the given (table) `name`.
849930
///
850931
/// The table id is returned.
@@ -856,7 +937,7 @@ unsafe fn call<T: Copy>(f: impl FnOnce(*mut T) -> u16) -> Result<T, Errno> {
856937
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
857938
/// - `NO_SUCH_TABLE`, when `name` is not the name of a table.
858939
#[inline]
859-
pub fn table_id_from_name(name: &str) -> Result<TableId, Errno> {
940+
pub fn table_id_from_name(name: &str) -> Result<TableId> {
860941
unsafe { call(|out| raw::table_id_from_name(name.as_ptr(), name.len(), out)) }
861942
}
862943

@@ -871,7 +952,7 @@ pub fn table_id_from_name(name: &str) -> Result<TableId, Errno> {
871952
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
872953
/// - `NO_SUCH_INDEX`, when `name` is not the name of an index.
873954
#[inline]
874-
pub fn index_id_from_name(name: &str) -> Result<IndexId, Errno> {
955+
pub fn index_id_from_name(name: &str) -> Result<IndexId> {
875956
unsafe { call(|out| raw::index_id_from_name(name.as_ptr(), name.len(), out)) }
876957
}
877958

@@ -884,7 +965,7 @@ pub fn index_id_from_name(name: &str) -> Result<IndexId, Errno> {
884965
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
885966
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
886967
#[inline]
887-
pub fn datastore_table_row_count(table_id: TableId) -> Result<u64, Errno> {
968+
pub fn datastore_table_row_count(table_id: TableId) -> Result<u64> {
888969
unsafe { call(|out| raw::datastore_table_row_count(table_id, out)) }
889970
}
890971

@@ -901,7 +982,7 @@ pub fn datastore_table_row_count(table_id: TableId) -> Result<u64, Errno> {
901982
/// - `row` doesn't decode from BSATN to a `ProductValue`
902983
/// according to the `ProductType` that the table's schema specifies.
903984
#[inline]
904-
pub fn datastore_insert_bsatn(table_id: TableId, row: &mut [u8]) -> Result<&[u8], Errno> {
985+
pub fn datastore_insert_bsatn(table_id: TableId, row: &mut [u8]) -> Result<&[u8]> {
905986
let row_ptr = row.as_mut_ptr();
906987
let row_len = &mut row.len();
907988
cvt(unsafe { raw::datastore_insert_bsatn(table_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
@@ -927,7 +1008,7 @@ pub fn datastore_insert_bsatn(table_id: TableId, row: &mut [u8]) -> Result<&[u8]
9271008
/// or if `row` cannot project to the index's type.
9281009
/// - the row was not found
9291010
#[inline]
930-
pub fn datastore_update_bsatn(table_id: TableId, index_id: IndexId, row: &mut [u8]) -> Result<&[u8], Errno> {
1011+
pub fn datastore_update_bsatn(table_id: TableId, index_id: IndexId, row: &mut [u8]) -> Result<&[u8]> {
9311012
let row_ptr = row.as_mut_ptr();
9321013
let row_len = &mut row.len();
9331014
cvt(unsafe { raw::datastore_update_bsatn(table_id, index_id, row_ptr, row_len) }).map(|()| &row[..*row_len])
@@ -954,7 +1035,7 @@ pub fn datastore_update_bsatn(table_id: TableId, index_id: IndexId, row: &mut [u
9541035
/// - `BSATN_DECODE_ERROR`, when `rel` cannot be decoded to `Vec<ProductValue>`
9551036
/// where each `ProductValue` is typed at the `ProductType` the table's schema specifies.
9561037
#[inline]
957-
pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> Result<u32, Errno> {
1038+
pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> Result<u32> {
9581039
unsafe { call(|out| raw::datastore_delete_all_by_eq_bsatn(table_id, relation.as_ptr(), relation.len(), out)) }
9591040
}
9601041

@@ -968,7 +1049,7 @@ pub fn datastore_delete_all_by_eq_bsatn(table_id: TableId, relation: &[u8]) -> R
9681049
///
9691050
/// - `NOT_IN_TRANSACTION`, when called outside of a transaction.
9701051
/// - `NO_SUCH_TABLE`, when `table_id` is not a known ID of a table.
971-
pub fn datastore_table_scan_bsatn(table_id: TableId) -> Result<RowIter, Errno> {
1052+
pub fn datastore_table_scan_bsatn(table_id: TableId) -> Result<RowIter> {
9721053
let raw = unsafe { call(|out| raw::datastore_table_scan_bsatn(table_id, out))? };
9731054
Ok(RowIter { raw })
9741055
}
@@ -1028,7 +1109,7 @@ pub fn datastore_index_scan_range_bsatn(
10281109
prefix_elems: ColId,
10291110
rstart: &[u8],
10301111
rend: &[u8],
1031-
) -> Result<RowIter, Errno> {
1112+
) -> Result<RowIter> {
10321113
let raw = unsafe {
10331114
call(|out| {
10341115
raw::datastore_index_scan_range_bsatn(
@@ -1076,7 +1157,7 @@ pub fn datastore_delete_by_index_scan_range_bsatn(
10761157
prefix_elems: ColId,
10771158
rstart: &[u8],
10781159
rend: &[u8],
1079-
) -> Result<u32, Errno> {
1160+
) -> Result<u32> {
10801161
unsafe {
10811162
call(|out| {
10821163
raw::datastore_delete_by_index_scan_range_bsatn(
@@ -1239,13 +1320,81 @@ impl Drop for RowIter {
12391320
}
12401321
}
12411322

1323+
#[cfg(feature = "unstable")]
12421324
pub mod procedure {
12431325
//! Side-effecting or asynchronous operations which only procedures are allowed to perform.
1326+
1327+
use super::{call, call_no_ret, raw, Result};
1328+
12441329
#[inline]
1245-
#[cfg(feature = "unstable")]
12461330
pub fn sleep_until(wake_at_timestamp: i64) -> i64 {
12471331
// Safety: Just calling an `extern "C"` function.
12481332
// Nothing weird happening here.
1249-
unsafe { super::raw::procedure_sleep_until(wake_at_timestamp) }
1333+
unsafe { raw::procedure_sleep_until(wake_at_timestamp) }
1334+
}
1335+
1336+
/// Starts a mutable transaction,
1337+
/// suspending execution of this WASM instance until
1338+
/// a mutable transaction lock is aquired.
1339+
///
1340+
/// Upon resuming, returns `Ok(timestamp)` on success,
1341+
/// enabling further calls that require a pending transaction,
1342+
/// or [`Errno`] otherwise.
1343+
///
1344+
/// # Errors
1345+
///
1346+
/// Returns an error:
1347+
///
1348+
/// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
1349+
#[inline]
1350+
pub fn procedure_start_mut_tx() -> Result<i64> {
1351+
unsafe { call(|out| raw::procedure_start_mut_tx(out)) }
1352+
}
1353+
1354+
/// Commits a mutable transaction,
1355+
/// suspending execution of this WASM instance until
1356+
/// the transaction has been committed
1357+
/// and subscription queries have been run and broadcast.
1358+
///
1359+
/// Upon resuming, returns `Ok(()` on success, or an [`Errno`] otherwise.
1360+
///
1361+
/// # Errors
1362+
///
1363+
/// Returns an error:
1364+
///
1365+
/// - `TRANSACTION_NOT_ANONYMOUS`,
1366+
/// if the transaction was not started in [`procedure_start_mut_tx`].
1367+
/// This can happen if this syscall is erroneously called by a reducer.
1368+
/// The code `NOT_IN_TRANSACTION` does not happen,
1369+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1370+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1371+
/// This currently does not happen as anonymous read transactions
1372+
/// are not exposed to modules.
1373+
#[inline]
1374+
pub fn procedure_commit_mut_tx() -> Result<()> {
1375+
call_no_ret(|| unsafe { raw::procedure_commit_mut_tx() })
1376+
}
1377+
1378+
/// Aborts a mutable transaction,
1379+
/// suspending execution of this WASM instance until
1380+
/// the transaction has been rolled back.
1381+
///
1382+
/// Upon resuming, returns `Ok(())` on success, or an [`Errno`] otherwise.
1383+
///
1384+
/// # Errors
1385+
///
1386+
/// Returns an error:
1387+
///
1388+
/// - `TRANSACTION_NOT_ANONYMOUS`,
1389+
/// if the transaction was not started in [`procedure_start_mut_tx`].
1390+
/// This can happen if this syscall is erroneously called by a reducer.
1391+
/// The code `NOT_IN_TRANSACTION` does not happen,
1392+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
1393+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
1394+
/// This currently does not happen as anonymous read transactions
1395+
/// are not exposed to modules.
1396+
#[inline]
1397+
pub fn procedure_abort_mut_tx() -> Result<()> {
1398+
call_no_ret(|| unsafe { raw::procedure_abort_mut_tx() })
12501399
}
12511400
}

0 commit comments

Comments
 (0)