mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-07-10 03:13:10 -04:00
d5c75d944d
# Description of Changes
Graduate **procedures** (and the outgoing HTTP client used from
procedures) out of the `unstable` feature gate /
`SPACETIMEDB_UNSTABLE_FEATURES` across all module libraries. The
`procedure` macro, `ProcedureContext`, `with_tx`/`try_with_tx`,
scheduled procedures, and `ctx.http` are now available without opting
into `unstable`.
**Still gated** (unchanged): HTTP handlers/webhooks, views, RLS /
`client_visibility_filter`, and immediate-scheduling
(`volatile_nonatomic_schedule_immediate`).
Per library:
- **`bindings-sys`**: ungate the `procedure` host-call module + raw ABI
imports (`procedure_start/commit/abort_mut_tx`,
`procedure_http_request`) and `call_no_ret`. Scheduling ABI stays gated.
- **`bindings` (Rust)**: ungate the `procedure` macro,
`ProcedureContext`, `TxContext`/`with_tx`/`try_with_tx`,
`db_read_only`/`get_read_only`, the procedure traits,
`register_procedure`, `__call_procedure__`, and procedure RNG. The
`http` module (previously gated as a unit) now has fine-grained gating
so the outgoing `HttpClient` is exposed while
`HandlerContext`/`Router`/handler macros stay gated.
- **`bindings-csharp`**: drop `[Experimental("STDB_UNSTABLE")]` from
`ProcedureContext.WithTx/TryWithTx` (runtime + codegen) and the
generated `ProcedureTxContext`; FFI snapshots regenerated. Handler
context members + RLS attribute stay gated.
- **`bindings-cpp`**: ungate the procedure ABI (`abi.h`/`FFI.h`),
`tx_execution.h`, the outgoing HTTP client (`http.h`/`http_convert.h`),
and `procedure_context.h`.
`handler_context.h`/`router.h`/`http_handler_macros.h` still `#error`
without `SPACETIMEDB_UNSTABLE_FEATURES`.
- **docs**: remove the procedures beta notices (HTTP handlers stay
marked beta for a later release); regenerate `static/llms.md`.
- **`sdk-test-procedure`**: no longer needs `features = ["unstable"]`.
# API and ABI breaking changes
Not breaking. This is purely additive to the stable surface: procedures
become available **without** the `unstable` feature, while modules that
already opt into `unstable` are unaffected. The wasm host-ABI imports
were already implemented host-side and merely gated module-side, so
there is no ABI version change. (No breaking-change label needed.)
# Expected complexity level and risk
**3/5.** The diff itself is mostly removing
`#[cfg]`/`#ifdef`/`[Experimental]` guards, but the gate bundled
procedures + outgoing HTTP + handlers + the ABI crate together, so the
work was in *separating* procedures from the features that stay gated.
Areas reviewers may want to scrutinize:
- The Rust `http` module went from "gated as a whole" to fine-grained
per-item gating; the outgoing `HttpClient` is exposed while handler
types/macros stay behind `unstable`.
- The `unstable` cut reaches the ABI crate (`bindings-sys`), which is
the only way procedures can compile without the feature.
- C++ separation of the outgoing HTTP client from HTTP handlers across
several headers.
# Testing
- [x] Rust: `cargo check -p spacetimedb` passes in default, `--features
unstable`, and `--no-default-features --features unstable`.
- [x] Rust end-to-end: `sdk-test-procedure` (uses procedures,
`with_tx`/`try_with_tx`, `ctx.http.get`, `new_uuid_v7`, scheduled
procedures) builds **without** `unstable`.
- [x] C#: Codegen + Runtime build; `Codegen.Tests` pass (6/6) after FFI
snapshot regen.
- [x] C++: host syntax-check confirms procedures + `ctx.http.send()`
compile **without** the flag, full headers compile **with** it, and
`handler_context.h` still `#error`s without it. (Not built for the real
wasm/emscripten target locally — relying on CI.)
- [x] `cargo ci lint` components reproduced locally: rustfmt, clippy
(`-D warnings`, default + `unstable`), csharpier, and `cargo doc --deny
warnings`.
- [ ] Reviewer: confirm the wasm bindings + C#/C++ test suites pass in
CI (especially the C++ wasm build, which couldn't run locally).
---------
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
147 lines
4.4 KiB
C++
147 lines
4.4 KiB
C++
#ifndef SPACETIMEDB_INTERNAL_TX_EXECUTION_H
|
|
#define SPACETIMEDB_INTERNAL_TX_EXECUTION_H
|
|
|
|
#include <spacetimedb/abi/FFI.h>
|
|
#include <spacetimedb/outcome.h>
|
|
#include <spacetimedb/tx_context.h>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace SpacetimeDB::Internal {
|
|
|
|
template<typename T>
|
|
struct is_outcome : std::false_type {};
|
|
|
|
template<typename T>
|
|
struct is_outcome<Outcome<T>> : std::true_type {};
|
|
|
|
template<typename T>
|
|
inline constexpr bool is_outcome_v = is_outcome<std::remove_cv_t<std::remove_reference_t<T>>>::value;
|
|
|
|
template<typename T>
|
|
bool tx_result_should_commit(const T& result) {
|
|
using ResultType = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
// TODO(http-handlers-cpp): Consider tightening try_with_tx in a future breaking release
|
|
// so rollback-aware callbacks use Outcome<T> (and possibly bool for compatibility)
|
|
// instead of silently treating arbitrary return types as commit-on-success.
|
|
if constexpr (std::is_same_v<ResultType, bool>) {
|
|
return result;
|
|
} else if constexpr (is_outcome_v<ResultType>) {
|
|
return result.is_ok();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class TxAbortGuard {
|
|
public:
|
|
TxAbortGuard() = default;
|
|
TxAbortGuard(const TxAbortGuard&) = delete;
|
|
TxAbortGuard& operator=(const TxAbortGuard&) = delete;
|
|
|
|
~TxAbortGuard() {
|
|
if (!armed_) {
|
|
return;
|
|
}
|
|
Status status = FFI::procedure_abort_mut_tx();
|
|
if (is_error(status)) {
|
|
LOG_PANIC("Failed to abort transaction");
|
|
}
|
|
}
|
|
|
|
void disarm() {
|
|
armed_ = false;
|
|
}
|
|
|
|
private:
|
|
bool armed_ = true;
|
|
};
|
|
|
|
inline void commit_tx_or_panic() {
|
|
Status status = FFI::procedure_commit_mut_tx();
|
|
if (is_error(status)) {
|
|
LOG_PANIC("Failed to commit transaction");
|
|
}
|
|
}
|
|
|
|
inline bool try_commit_tx() {
|
|
return is_ok(FFI::procedure_commit_mut_tx());
|
|
}
|
|
|
|
inline void abort_tx_or_panic() {
|
|
Status status = FFI::procedure_abort_mut_tx();
|
|
if (is_error(status)) {
|
|
LOG_PANIC("Failed to abort transaction");
|
|
}
|
|
}
|
|
|
|
template<typename MakeReducerContext, typename Func>
|
|
auto run_tx_once(MakeReducerContext&& make_reducer_ctx, Func& body) -> decltype(body(std::declval<TxContext&>())) {
|
|
using ResultType = decltype(body(std::declval<TxContext&>()));
|
|
|
|
int64_t tx_timestamp = 0;
|
|
Status status = FFI::procedure_start_mut_tx(&tx_timestamp);
|
|
if (is_error(status)) {
|
|
LOG_PANIC("Failed to start transaction");
|
|
}
|
|
|
|
TxAbortGuard abort_guard;
|
|
ReducerContext reducer_ctx = make_reducer_ctx(Timestamp::from_micros_since_epoch(tx_timestamp));
|
|
TxContext tx{reducer_ctx};
|
|
|
|
if constexpr (std::is_void_v<ResultType>) {
|
|
body(tx);
|
|
abort_guard.disarm();
|
|
} else {
|
|
ResultType result = body(tx);
|
|
abort_guard.disarm();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
template<typename MakeReducerContext, typename Func>
|
|
auto with_tx(MakeReducerContext&& make_reducer_ctx, Func& body) -> decltype(body(std::declval<TxContext&>())) {
|
|
using ResultType = decltype(body(std::declval<TxContext&>()));
|
|
|
|
if constexpr (std::is_void_v<ResultType>) {
|
|
run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
if (!try_commit_tx()) {
|
|
run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
commit_tx_or_panic();
|
|
}
|
|
} else {
|
|
ResultType result = run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
if (!try_commit_tx()) {
|
|
result = run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
commit_tx_or_panic();
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
template<typename MakeReducerContext, typename Func>
|
|
auto try_with_tx(MakeReducerContext&& make_reducer_ctx, Func& body) -> decltype(body(std::declval<TxContext&>())) {
|
|
using ResultType = decltype(body(std::declval<TxContext&>()));
|
|
|
|
ResultType result = run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
if (!tx_result_should_commit(result)) {
|
|
abort_tx_or_panic();
|
|
return result;
|
|
}
|
|
|
|
if (!try_commit_tx()) {
|
|
result = run_tx_once(std::forward<MakeReducerContext>(make_reducer_ctx), body);
|
|
if (tx_result_should_commit(result)) {
|
|
commit_tx_or_panic();
|
|
} else {
|
|
abort_tx_or_panic();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace SpacetimeDB::Internal
|
|
|
|
#endif // SPACETIMEDB_INTERNAL_TX_EXECUTION_H
|