Skip to content

Commit 792873a

Browse files
committed
procedures: add wasm abi for anon mut txes
1 parent 3ea5fde commit 792873a

10 files changed

Lines changed: 415 additions & 121 deletions

File tree

crates/bindings-sys/src/lib.rs

Lines changed: 70 additions & 1 deletion
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+
/// This function does not trap.
678+
///
679+
/// # Errors
680+
///
681+
/// Returns an error:
682+
///
683+
/// - `WOULD_BLOCK_TRANSACTION`, if there's already an ongoing transaction.
684+
pub fn procedure_start_mut_transaction() -> u16;
685+
686+
/// Commits a mutable transaction,
687+
/// suspending execution of this WASM instance until
688+
/// the transaction has been committed
689+
/// and subscription queries have been run and broadcast.
690+
///
691+
/// Upon resuming, returns `0` on success, or an error code otherwise.
692+
///
693+
/// # Traps
694+
///
695+
/// This function does not trap.
696+
///
697+
/// # Errors
698+
///
699+
/// Returns an error:
700+
///
701+
/// - `TRANSACTION_NOT_ANONYMOUS`,
702+
/// if the transaction was not started in [`procedure_start_mut_transaction`].
703+
/// This can happen if this syscall is erroneously called by a reducer.
704+
/// The code `NOT_IN_TRANSACTION` does not happen,
705+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
706+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
707+
/// This currently does not happen as anonymous read transactions
708+
/// are not exposed to modules.
709+
pub fn procedure_commit_mut_transaction() -> u16;
710+
711+
/// Aborts a mutable transaction,
712+
/// suspending execution of this WASM instance until
713+
/// the transaction has been rolled back.
714+
///
715+
/// Upon resuming, returns `0` on success, or an error code otherwise.
716+
///
717+
/// # Traps
718+
///
719+
/// This function does not trap.
720+
///
721+
/// # Errors
722+
///
723+
/// Returns an error:
724+
///
725+
/// - `TRANSACTION_NOT_ANONYMOUS`,
726+
/// if the transaction was not started in [`procedure_start_mut_transaction`].
727+
/// This can happen if this syscall is erroneously called by a reducer.
728+
/// The code `NOT_IN_TRANSACTION` does not happen,
729+
/// as it is subsumed by `TRANSACTION_NOT_ANONYMOUS`.
730+
/// - `TRANSACTION_IS_READ_ONLY`, if the pending transaction is read-only.
731+
/// This currently does not happen as anonymous read transactions
732+
/// are not exposed to modules.
733+
pub fn procedure_abort_mut_transaction() -> u16;
734+
666735
}
667736

668737
/// What strategy does the database index use?

crates/core/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ pub enum NodesError {
266266
BadColumn,
267267
#[error("can't perform operation; not inside transaction")]
268268
NotInTransaction,
269+
#[error("can't perform operation; a transaction already exists")]
270+
WouldBlockTransaction,
269271
#[error("table with name {0:?} already exists")]
270272
AlreadyExists(String),
271273
#[error("table with name `{0}` start with 'st_' and that is reserved for internal system tables.")]

crates/core/src/host/instance_env.rs

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ use crate::db::relational_db::{MutTx, RelationalDB};
44
use crate::error::{DBError, DatastoreError, IndexError, NodesError};
55
use crate::host::wasm_common::TimingSpan;
66
use crate::replica_context::ReplicaContext;
7+
use crate::util::asyncify;
78
use chrono::{DateTime, Utc};
89
use core::mem;
910
use parking_lot::{Mutex, MutexGuard};
1011
use smallvec::SmallVec;
12+
use spacetimedb_datastore::execution_context::Workload;
1113
use spacetimedb_datastore::locking_tx_datastore::state_view::StateView;
1214
use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, MutTxId};
15+
use spacetimedb_datastore::traits::IsolationLevel;
1316
use spacetimedb_lib::{ConnectionId, Identity, Timestamp};
1417
use spacetimedb_primitives::{ColId, ColList, IndexId, TableId};
1518
use spacetimedb_sats::{
@@ -204,6 +207,14 @@ impl InstanceEnv {
204207
self.tx.get()
205208
}
206209

210+
pub(crate) fn take_tx(&self) -> Result<MutTxId, GetTxError> {
211+
self.tx.take()
212+
}
213+
214+
pub(crate) fn relational_db(&self) -> &Arc<RelationalDB> {
215+
&self.replica_ctx.relational_db
216+
}
217+
207218
pub(crate) fn get_jwt_payload(&self, connection_id: ConnectionId) -> Result<Option<String>, NodesError> {
208219
let tx = &mut *self.get_tx()?;
209220
Ok(tx.get_jwt_payload(connection_id).map_err(DBError::from)?)
@@ -274,7 +285,7 @@ impl InstanceEnv {
274285
}
275286

276287
pub fn insert(&self, table_id: TableId, buffer: &mut [u8]) -> Result<usize, NodesError> {
277-
let stdb = &*self.replica_ctx.relational_db;
288+
let stdb = self.relational_db();
278289
let tx = &mut *self.get_tx()?;
279290

280291
let (row_len, row_ptr, insert_flags) = stdb
@@ -343,7 +354,7 @@ impl InstanceEnv {
343354
}
344355

345356
pub fn update(&self, table_id: TableId, index_id: IndexId, buffer: &mut [u8]) -> Result<usize, NodesError> {
346-
let stdb = &*self.replica_ctx.relational_db;
357+
let stdb = self.relational_db();
347358
let tx = &mut *self.get_tx()?;
348359

349360
let (row_len, row_ptr, update_flags) = stdb
@@ -386,8 +397,8 @@ impl InstanceEnv {
386397
rstart: &[u8],
387398
rend: &[u8],
388399
) -> Result<u32, NodesError> {
389-
let stdb = &*self.replica_ctx.relational_db;
390-
let tx = &mut *self.tx.get()?;
400+
let stdb = self.relational_db();
401+
let tx = &mut *self.get_tx()?;
391402

392403
// Find all rows in the table to delete.
393404
let (table_id, _, _, iter) = stdb.index_scan_range(tx, index_id, prefix, prefix_elems, rstart, rend)?;
@@ -416,7 +427,7 @@ impl InstanceEnv {
416427
/// - a row couldn't be decoded to the table schema type.
417428
#[tracing::instrument(level = "trace", skip(self, relation))]
418429
pub fn datastore_delete_all_by_eq_bsatn(&self, table_id: TableId, relation: &[u8]) -> Result<u32, NodesError> {
419-
let stdb = &*self.replica_ctx.relational_db;
430+
let stdb = self.relational_db();
420431
let tx = &mut *self.get_tx()?;
421432

422433
// Track the number of bytes coming from the caller
@@ -443,7 +454,7 @@ impl InstanceEnv {
443454
/// and `TableNotFound` if the table does not exist.
444455
#[tracing::instrument(level = "trace", skip_all)]
445456
pub fn table_id_from_name(&self, table_name: &str) -> Result<TableId, NodesError> {
446-
let stdb = &*self.replica_ctx.relational_db;
457+
let stdb = self.relational_db();
447458
let tx = &mut *self.get_tx()?;
448459

449460
// Query the table id from the name.
@@ -457,7 +468,7 @@ impl InstanceEnv {
457468
/// and `IndexNotFound` if the index does not exist.
458469
#[tracing::instrument(level = "trace", skip_all)]
459470
pub fn index_id_from_name(&self, index_name: &str) -> Result<IndexId, NodesError> {
460-
let stdb = &*self.replica_ctx.relational_db;
471+
let stdb = self.relational_db();
461472
let tx = &mut *self.get_tx()?;
462473

463474
// Query the index id from the name.
@@ -471,7 +482,7 @@ impl InstanceEnv {
471482
/// and `TableNotFound` if the table does not exist.
472483
#[tracing::instrument(level = "trace", skip_all)]
473484
pub fn datastore_table_row_count(&self, table_id: TableId) -> Result<u64, NodesError> {
474-
let stdb = &*self.replica_ctx.relational_db;
485+
let stdb = self.relational_db();
475486
let tx = &mut *self.get_tx()?;
476487

477488
// Query the row count for id.
@@ -488,8 +499,8 @@ impl InstanceEnv {
488499
pool: &mut ChunkPool,
489500
table_id: TableId,
490501
) -> Result<Vec<Vec<u8>>, NodesError> {
491-
let stdb = &*self.replica_ctx.relational_db;
492-
let tx = &mut *self.tx.get()?;
502+
let stdb = self.relational_db();
503+
let tx = &mut *self.get_tx()?;
493504

494505
// Track the number of rows and the number of bytes scanned by the iterator
495506
let mut rows_scanned = 0;
@@ -521,8 +532,8 @@ impl InstanceEnv {
521532
rstart: &[u8],
522533
rend: &[u8],
523534
) -> Result<Vec<Vec<u8>>, NodesError> {
524-
let stdb = &*self.replica_ctx.relational_db;
525-
let tx = &mut *self.tx.get()?;
535+
let stdb = self.relational_db();
536+
let tx = &mut *self.get_tx()?;
526537

527538
// Track rows and bytes scanned by the iterator
528539
let mut rows_scanned = 0;
@@ -569,26 +580,52 @@ impl InstanceEnv {
569580

570581
written
571582
}
583+
584+
pub async fn start_mutable_tx(&mut self) -> Result<(), NodesError> {
585+
if self.get_tx().is_ok() {
586+
return Err(NodesError::WouldBlockTransaction);
587+
}
588+
589+
let stdb = self.replica_ctx.relational_db.clone();
590+
// TODO(procedure-tx): should we add a new workload, e.g., `AnonTx`?
591+
let tx = asyncify(move || stdb.begin_mut_tx(IsolationLevel::Serializable, Workload::Internal)).await;
592+
self.tx.set_raw(tx);
593+
594+
Ok(())
595+
}
572596
}
573597

574598
impl TxSlot {
575-
pub fn set<T>(&mut self, tx: MutTxId, f: impl FnOnce() -> T) -> (MutTxId, T) {
599+
/// Sets the slot to `tx`, ensuring that there was no tx before.
600+
pub fn set_raw(&mut self, tx: MutTxId) {
576601
let prev = self.inner.lock().replace(tx);
577602
assert!(prev.is_none(), "reentrant TxSlot::set");
578-
let remove_tx = || self.inner.lock().take();
603+
}
604+
605+
/// Sets the slot to `tx` runs `work`, and returns back `tx`.
606+
pub fn set<T>(&mut self, tx: MutTxId, work: impl FnOnce() -> T) -> (MutTxId, T) {
607+
self.set_raw(tx);
608+
609+
let remove_tx = || self.take().expect("tx was removed during transaction");
579610

580611
let res = {
581612
scopeguard::defer_on_unwind! { remove_tx(); }
582-
f()
613+
work()
583614
};
584615

585-
let tx = remove_tx().expect("tx was removed during transaction");
616+
let tx = remove_tx();
586617
(tx, res)
587618
}
588619

620+
/// Returns the tx in the slot.
589621
pub fn get(&self) -> Result<impl DerefMut<Target = MutTxId> + '_, GetTxError> {
590622
MutexGuard::try_map(self.inner.lock(), |map| map.as_mut()).map_err(|_| GetTxError)
591623
}
624+
625+
/// Steals th tx from the slot.
626+
pub fn take(&self) -> Result<MutTxId, GetTxError> {
627+
self.inner.lock().take().ok_or(GetTxError)
628+
}
592629
}
593630

594631
#[derive(Debug)]

crates/core/src/host/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,7 @@ pub enum AbiCall {
178178
VolatileNonatomicScheduleImmediate,
179179

180180
ProcedureSleepUntil,
181+
ProcedureStartMutTransaction,
182+
ProcedureCommitMutTransaction,
183+
ProcedureAbortMutTransaction,
181184
}

crates/core/src/host/wasm_common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ pub(super) type TimingSpanSet = ResourceSlab<TimingSpanIdx>;
347347
pub fn err_to_errno(err: &NodesError) -> Option<NonZeroU16> {
348348
match err {
349349
NodesError::NotInTransaction => Some(errno::NOT_IN_TRANSACTION),
350+
NodesError::WouldBlockTransaction => Some(errno::WOULD_BLOCK_TRANSACTION),
350351
NodesError::DecodeRow(_) => Some(errno::BSATN_DECODE_ERROR),
351352
NodesError::TableNotFound => Some(errno::NO_SUCH_TABLE),
352353
NodesError::IndexNotFound => Some(errno::NO_SUCH_INDEX),
@@ -422,6 +423,9 @@ macro_rules! abi_funcs {
422423

423424
$link_async! {
424425
"spacetime_10.3"::procedure_sleep_until,
426+
"spacetime_10.3"::procedure_start_mut_transaction,
427+
"spacetime_10.3"::procedure_commit_mut_transaction,
428+
"spacetime_10.3"::procedure_abort_mut_transaction,
425429
}
426430
};
427431
}

0 commit comments

Comments
 (0)