Skip to content

Commit afe169a

Browse files
authored
Fix the issues with scheduling procedures (#3816)
# Description of Changes This reapplies the patch from #3704, and fixes the issues that were causing it to deadlock. The reason it was deadlocking was that it allowed for the following sequence of events: * `SchedulerActor::handle_queued()` begins mutable tx * `ModuleHost::disconnect_client()` submits call to `call_reducer(tx: None)` * scheduler submits call to `call_reducer(tx: Some)` * `WasmModuleInstance::disconnect_client` now has to try to take tx lock, but the scheduler's call_reducer already holds it and is behind it in the queue So, I moved most of the logic from `handle_queued` back to being executed in the module worker thread, but kept the code in `scheduler.rs` so that it can all be reasoned about locally. Fixes #3645. Should I uncomment the implementation of `ExportFunctionForScheduledTable for F: Procedure` now? # Expected complexity level and risk 2 - there's a chance that this patch hasn't fully fixed the deadlock issue from #3704, but I'm quite confident. # Testing - [x] Manually verified that deadlock no longer occurs - previously, `while true; do python -m smoketests schedule_reducer -k test_scheduled_table_subscription; done` would freeze up in only 2 or 3 iterations, but now it can run for 10 minutes without issues.
1 parent e0dc9fb commit afe169a

219 files changed

Lines changed: 1653 additions & 384 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/bindings/src/lib.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -731,17 +731,16 @@ pub use spacetimedb_bindings_macro::reducer;
731731
// TODO(procedure-http): add example with an HTTP request.
732732
// TODO(procedure-transaction): document obtaining and using a transaction within a procedure.
733733
///
734-
// TODO(scheduled-procedures): Uncomment below docs.
735-
// /// # Scheduled procedures
734+
/// # Scheduled procedures
736735
// TODO(docs): after moving scheduled reducer docs into table secion, link there.
737-
// ///
738-
// /// Like [reducer]s, procedures can be made **scheduled**.
739-
// /// This allows calling procedures at a particular time, or in a loop.
740-
// /// It also allows reducers to enqueue procedure runs.
741-
// ///
742-
// /// Scheduled procedures are called on a best-effort basis and may be slightly delayed in their execution
743-
// /// when a database is under heavy load.
744-
// ///
736+
///
737+
/// Like [reducer]s, procedures can be made **scheduled**.
738+
/// This allows calling procedures at a particular time, or in a loop.
739+
/// It also allows reducers to enqueue procedure runs.
740+
///
741+
/// Scheduled procedures are called on a best-effort basis and may be slightly delayed in their execution
742+
/// when a database is under heavy load.
743+
///
745744
/// [clients]: https://spacetimedb.com/docs/#client
746745
// TODO(procedure-async): update docs and examples with `async`-ness.
747746
#[doc(inline)]

crates/bindings/src/rt.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,7 @@ pub struct FnKindView {
444444
/// See <https://willcrichton.net/notes/defeating-coherence-rust/> for details on this technique.
445445
#[cfg_attr(
446446
feature = "unstable",
447-
// TODO(scheduled-procedures): uncomment this, delete other line
448-
// doc = "It will be one of [`FnKindReducer`] or [`FnKindProcedure`] in modules that compile successfully."
449-
doc = "It will be [`FnKindReducer`] in modules that compile successfully."
447+
doc = "It will be one of [`FnKindReducer`] or [`FnKindProcedure`] in modules that compile successfully."
450448
)]
451449
#[cfg_attr(
452450
not(feature = "unstable"),
@@ -467,16 +465,15 @@ impl<'de, TableRow: SpacetimeType + Serialize + Deserialize<'de>, F: Reducer<'de
467465
{
468466
}
469467

470-
// TODO(scheduled-procedures): uncomment this to syntactically allow scheduled procedures.
471-
// #[cfg(feature = "unstable")]
472-
// impl<
473-
// 'de,
474-
// TableRow: SpacetimeType + Serialize + Deserialize<'de>,
475-
// Ret: SpacetimeType + Serialize + Deserialize<'de>,
476-
// F: Procedure<'de, (TableRow,), Ret>,
477-
// > ExportFunctionForScheduledTable<'de, TableRow, FnKindProcedure<Ret>> for F
478-
// {
479-
// }
468+
#[cfg(feature = "unstable")]
469+
impl<
470+
'de,
471+
TableRow: SpacetimeType + Serialize + Deserialize<'de>,
472+
Ret: SpacetimeType + Serialize + Deserialize<'de>,
473+
F: Procedure<'de, (TableRow,), Ret>,
474+
> ExportFunctionForScheduledTable<'de, TableRow, FnKindProcedure<Ret>> for F
475+
{
476+
}
480477

481478
// the macro generates <T as SpacetimeType>::make_type::<DummyTypespace>
482479
pub struct DummyTypespace;

crates/bindings/tests/ui/views.stderr

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -361,29 +361,3 @@ error[E0277]: the trait bound `NotSpacetimeType: SpacetimeType` is not satisfied
361361
ColId
362362
and $N others
363363
= note: required for `Option<NotSpacetimeType>` to implement `SpacetimeType`
364-
365-
error[E0631]: type mismatch in function arguments
366-
--> tests/ui/views.rs:142:56
367-
|
368-
142 | #[spacetimedb::table(name = scheduled_table, scheduled(scheduled_table_view))]
369-
| -------------------------------------------------------^^^^^^^^^^^^^^^^^^^^---
370-
| | |
371-
| | expected due to this
372-
| required by a bound introduced by this call
373-
...
374-
154 | fn scheduled_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec<Player> {
375-
| ------------------------------------------------------------------------------ found signature defined here
376-
|
377-
= note: expected function signature `for<'a> fn(&'a ReducerContext, ScheduledTable) -> _`
378-
found function signature `fn(&ViewContext, ScheduledTable) -> _`
379-
= note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec<Player> {scheduled_table_view}` to implement `Reducer<'_, (ScheduledTable,)>`
380-
= note: required for `for<'a> fn(&'a ViewContext, ScheduledTable) -> Vec<Player> {scheduled_table_view}` to implement `ExportFunctionForScheduledTable<'_, ScheduledTable, {type error}>`
381-
note: required by a bound in `scheduled_typecheck`
382-
--> src/rt.rs
383-
|
384-
| pub const fn scheduled_typecheck<'de, Row, FnKind>(_x: impl ExportFunctionForScheduledTable<'de, Row, FnKind>)
385-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `scheduled_typecheck`
386-
help: consider wrapping the function in a closure
387-
|
388-
142 | #[spacetimedb::table(name = scheduled_table, scheduled(|arg0: &ReducerContext, arg1: ScheduledTable| scheduled_table_view(/* &ViewContext */, arg1)))]
389-
| +++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++

crates/codegen/src/rust.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ impl {func_name} for super::RemoteReducers {{
423423
{callback_id}(self.imp.on_reducer(
424424
{reducer_name:?},
425425
Box::new(move |ctx: &super::ReducerEventContext| {{
426+
#[allow(irrefutable_let_patterns)]
426427
let super::ReducerEventContext {{
427428
event: __sdk::ReducerEvent {{
428429
reducer: super::Reducer::{enum_variant_name} {{

crates/codegen/tests/snapshots/codegen__codegen_rust.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ impl add_player for super::RemoteReducers {
7272
AddPlayerCallbackId(self.imp.on_reducer(
7373
"add_player",
7474
Box::new(move |ctx: &super::ReducerEventContext| {
75+
#[allow(irrefutable_let_patterns)]
7576
let super::ReducerEventContext {
7677
event: __sdk::ReducerEvent {
7778
reducer: super::Reducer::AddPlayer {
@@ -181,6 +182,7 @@ impl add_private for super::RemoteReducers {
181182
AddPrivateCallbackId(self.imp.on_reducer(
182183
"add_private",
183184
Box::new(move |ctx: &super::ReducerEventContext| {
185+
#[allow(irrefutable_let_patterns)]
184186
let super::ReducerEventContext {
185187
event: __sdk::ReducerEvent {
186188
reducer: super::Reducer::AddPrivate {
@@ -294,6 +296,7 @@ age: u8,
294296
AddCallbackId(self.imp.on_reducer(
295297
"add",
296298
Box::new(move |ctx: &super::ReducerEventContext| {
299+
#[allow(irrefutable_let_patterns)]
297300
let super::ReducerEventContext {
298301
event: __sdk::ReducerEvent {
299302
reducer: super::Reducer::Add {
@@ -398,6 +401,7 @@ impl assert_caller_identity_is_module_identity for super::RemoteReducers {
398401
AssertCallerIdentityIsModuleIdentityCallbackId(self.imp.on_reducer(
399402
"assert_caller_identity_is_module_identity",
400403
Box::new(move |ctx: &super::ReducerEventContext| {
404+
#[allow(irrefutable_let_patterns)]
401405
let super::ReducerEventContext {
402406
event: __sdk::ReducerEvent {
403407
reducer: super::Reducer::AssertCallerIdentityIsModuleIdentity {
@@ -527,6 +531,7 @@ impl client_connected for super::RemoteReducers {
527531
ClientConnectedCallbackId(self.imp.on_reducer(
528532
"client_connected",
529533
Box::new(move |ctx: &super::ReducerEventContext| {
534+
#[allow(irrefutable_let_patterns)]
530535
let super::ReducerEventContext {
531536
event: __sdk::ReducerEvent {
532537
reducer: super::Reducer::ClientConnected {
@@ -636,6 +641,7 @@ impl delete_player for super::RemoteReducers {
636641
DeletePlayerCallbackId(self.imp.on_reducer(
637642
"delete_player",
638643
Box::new(move |ctx: &super::ReducerEventContext| {
644+
#[allow(irrefutable_let_patterns)]
639645
let super::ReducerEventContext {
640646
event: __sdk::ReducerEvent {
641647
reducer: super::Reducer::DeletePlayer {
@@ -745,6 +751,7 @@ impl delete_players_by_name for super::RemoteReducers {
745751
DeletePlayersByNameCallbackId(self.imp.on_reducer(
746752
"delete_players_by_name",
747753
Box::new(move |ctx: &super::ReducerEventContext| {
754+
#[allow(irrefutable_let_patterns)]
748755
let super::ReducerEventContext {
749756
event: __sdk::ReducerEvent {
750757
reducer: super::Reducer::DeletePlayersByName {
@@ -1066,6 +1073,7 @@ impl list_over_age for super::RemoteReducers {
10661073
ListOverAgeCallbackId(self.imp.on_reducer(
10671074
"list_over_age",
10681075
Box::new(move |ctx: &super::ReducerEventContext| {
1076+
#[allow(irrefutable_let_patterns)]
10691077
let super::ReducerEventContext {
10701078
event: __sdk::ReducerEvent {
10711079
reducer: super::Reducer::ListOverAge {
@@ -1170,6 +1178,7 @@ impl log_module_identity for super::RemoteReducers {
11701178
LogModuleIdentityCallbackId(self.imp.on_reducer(
11711179
"log_module_identity",
11721180
Box::new(move |ctx: &super::ReducerEventContext| {
1181+
#[allow(irrefutable_let_patterns)]
11731182
let super::ReducerEventContext {
11741183
event: __sdk::ReducerEvent {
11751184
reducer: super::Reducer::LogModuleIdentity {
@@ -3577,6 +3586,7 @@ impl query_private for super::RemoteReducers {
35773586
QueryPrivateCallbackId(self.imp.on_reducer(
35783587
"query_private",
35793588
Box::new(move |ctx: &super::ReducerEventContext| {
3589+
#[allow(irrefutable_let_patterns)]
35803590
let super::ReducerEventContext {
35813591
event: __sdk::ReducerEvent {
35823592
reducer: super::Reducer::QueryPrivate {
@@ -3887,6 +3897,7 @@ impl repeating_test for super::RemoteReducers {
38873897
RepeatingTestCallbackId(self.imp.on_reducer(
38883898
"repeating_test",
38893899
Box::new(move |ctx: &super::ReducerEventContext| {
3900+
#[allow(irrefutable_let_patterns)]
38903901
let super::ReducerEventContext {
38913902
event: __sdk::ReducerEvent {
38923903
reducer: super::Reducer::RepeatingTest {
@@ -4050,6 +4061,7 @@ impl say_hello for super::RemoteReducers {
40504061
SayHelloCallbackId(self.imp.on_reducer(
40514062
"say_hello",
40524063
Box::new(move |ctx: &super::ReducerEventContext| {
4064+
#[allow(irrefutable_let_patterns)]
40534065
let super::ReducerEventContext {
40544066
event: __sdk::ReducerEvent {
40554067
reducer: super::Reducer::SayHello {
@@ -4460,6 +4472,7 @@ impl test_btree_index_args for super::RemoteReducers {
44604472
TestBtreeIndexArgsCallbackId(self.imp.on_reducer(
44614473
"test_btree_index_args",
44624474
Box::new(move |ctx: &super::ReducerEventContext| {
4475+
#[allow(irrefutable_let_patterns)]
44634476
let super::ReducerEventContext {
44644477
event: __sdk::ReducerEvent {
44654478
reducer: super::Reducer::TestBtreeIndexArgs {
@@ -5013,6 +5026,7 @@ arg_4: NamespaceTestF,
50135026
TestCallbackId(self.imp.on_reducer(
50145027
"test",
50155028
Box::new(move |ctx: &super::ReducerEventContext| {
5029+
#[allow(irrefutable_let_patterns)]
50165030
let super::ReducerEventContext {
50175031
event: __sdk::ReducerEvent {
50185032
reducer: super::Reducer::Test {

crates/core/src/host/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use spacetimedb_lib::bsatn;
88
use spacetimedb_lib::de::{serde::SeedWrapper, DeserializeSeed};
99
use spacetimedb_lib::ProductValue;
1010
use spacetimedb_schema::def::deserialize::{ArgsSeed, FunctionDef};
11+
use spacetimedb_schema::def::ModuleDef;
1112

1213
mod disk_storage;
1314
mod host_controller;
@@ -41,6 +42,14 @@ pub enum FunctionArgs {
4142
}
4243

4344
impl FunctionArgs {
45+
fn into_tuple_for_def<Def: FunctionDef>(
46+
self,
47+
module: &ModuleDef,
48+
def: &Def,
49+
) -> Result<ArgsTuple, InvalidFunctionArguments> {
50+
self.into_tuple(module.arg_seed_for(def))
51+
}
52+
4453
fn into_tuple<Def: FunctionDef>(self, seed: ArgsSeed<'_, Def>) -> Result<ArgsTuple, InvalidFunctionArguments> {
4554
self._into_tuple(seed).map_err(|err| InvalidFunctionArguments {
4655
err,

0 commit comments

Comments
 (0)