Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/core/src/db/relational_db.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::db::durability::DurabilityWorker;
use crate::db::MetricsRecorderQueue;
use crate::error::{DBError, RestoreSnapshotError};
use crate::messages::control_db::HostType;
use crate::subscription::ExecutionCounters;
use crate::util::asyncify;
use crate::worker_metrics::WORKER_METRICS;
Expand Down Expand Up @@ -464,7 +463,7 @@ impl RelationalDB {
/// The caller must ensure that:
///
/// - `program.hash` is the [`Hash`] over `program.bytes`.
/// - `program.bytes` is a valid module acc. to `host_type`.
/// - `program.bytes` is a valid module acc. to `program.host_type`.
/// - the schema updates contained in the module have been applied within
/// the transactional context `tx`.
/// - the `__init__` reducer contained in the module has been executed
Expand Down Expand Up @@ -1861,6 +1860,7 @@ fn default_row_count_fn(db: Identity) -> RowCountFn {
pub mod tests_utils {
use crate::db::snapshot;
use crate::db::snapshot::SnapshotWorker;
use crate::messages::control_db::HostType;

use super::*;
use core::ops::Deref;
Expand Down
105 changes: 86 additions & 19 deletions crates/core/src/host/host_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use spacetimedb_data_structures::error_stream::ErrorStream;
use spacetimedb_data_structures::map::{IntMap, IntSet};
use spacetimedb_datastore::db_metrics::data_size::DATA_SIZE_METRICS;
use spacetimedb_datastore::db_metrics::DB_METRICS;
use spacetimedb_datastore::execution_context::Workload;
use spacetimedb_datastore::system_tables::ModuleKind;
use spacetimedb_datastore::traits::Program;
use spacetimedb_durability::{self as durability};
use spacetimedb_lib::{AlgebraicValue, Identity, Timestamp};
Expand Down Expand Up @@ -898,7 +900,6 @@ impl Host {
bsatn_rlb_pool,
..
} = host_controller;
let on_panic = host_controller.unregister_fn(replica_id);
let replica_dir = data_dir.replica(replica_id);
let (tx_metrics_queue, tx_metrics_recorder_task) = spawn_tx_metrics_recorder();

Expand Down Expand Up @@ -947,7 +948,7 @@ impl Host {
(db, clients)
}
};
let (program, program_needs_init) = match db.program()? {
let (mut program, program_needs_init) = match db.program()? {
// Launch module with program from existing database.
Some(program) => {
info!(
Expand All @@ -974,23 +975,89 @@ impl Host {
}
};

let (program, launched) = ModuleLauncher {
database,
replica_id,
program,
on_panic,
relational_db: Arc::new(db),
energy_monitor: energy_monitor.clone(),
module_logs: match config.storage {
db::Storage::Memory => None,
db::Storage::Disk => Some(replica_dir.module_logs()),
},
runtimes: runtimes.clone(),
core: host_controller.db_cores.take(),
bsatn_rlb_pool: bsatn_rlb_pool.clone(),
}
.launch_module()
.await?;
let relational_db = Arc::new(db);
let (program, launched) = match HostType::from(program.kind) {
HostType::Js => {
ModuleLauncher {
database,
replica_id,
program,
on_panic: host_controller.unregister_fn(replica_id),
relational_db,
energy_monitor: energy_monitor.clone(),
module_logs: match config.storage {
db::Storage::Memory => None,
db::Storage::Disk => Some(replica_dir.module_logs()),
},
runtimes: runtimes.clone(),
core: host_controller.db_cores.take(),
bsatn_rlb_pool: bsatn_rlb_pool.clone(),
}
.launch_module()
.await?
}
HostType::Wasm => {
// Prior to https://github.com/clockworklabs/SpacetimeDB/pull/4549
// the host type in `st_module` was always set to wasm.
// We now correctly use the host type from the database, but the
// module may in fact be a JS module.
// So if launching it as a wasm module fails, try JS instead.
// If this succeeds, the module is definitely a JS module, so
// attempt to repair `st_module` in this case.
//
// TODO: This code should eventually be removed once all
// databases have been repaired.
let launch_wasm_result = ModuleLauncher {
database: database.clone(),
replica_id,
program: program.clone(),
on_panic: host_controller.unregister_fn(replica_id),
relational_db: relational_db.clone(),
energy_monitor: energy_monitor.clone(),
module_logs: match config.storage {
db::Storage::Memory => None,
db::Storage::Disk => Some(replica_dir.clone().module_logs()),
},
runtimes: runtimes.clone(),
core: host_controller.db_cores.take(),
bsatn_rlb_pool: bsatn_rlb_pool.clone(),
}
.launch_module()
.await;
match launch_wasm_result {
Ok(program_and_module_host) => program_and_module_host,
Err(e) => {
warn!("failed to launch wasm module, trying js: {e:#}");

program.kind = ModuleKind::JS;
let res = ModuleLauncher {
database,
replica_id,
program: program.clone(),
on_panic: host_controller.unregister_fn(replica_id),
relational_db: relational_db.clone(),
energy_monitor: energy_monitor.clone(),
module_logs: match config.storage {
db::Storage::Memory => None,
db::Storage::Disk => Some(replica_dir.module_logs()),
},
runtimes: runtimes.clone(),
core: host_controller.db_cores.take(),
bsatn_rlb_pool: bsatn_rlb_pool.clone(),
}
.launch_module()
.await;

if res.is_ok() {
let _ = relational_db
.with_auto_commit(Workload::Internal, |tx| relational_db.update_program(tx, program));
}

res?
}
}
}
};

if program_needs_init {
let call_result = launched.module_host.init_database(program).await?;
Expand Down
1 change: 1 addition & 0 deletions crates/datastore/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ pub struct Metadata {
}

/// Program associated with a database.
#[derive(Clone)]
pub struct Program {
/// Hash over the program's bytes.
pub hash: Hash,
Expand Down
1 change: 1 addition & 0 deletions crates/smoketests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ anyhow.workspace = true
which = "8.0.0"

[dev-dependencies]
spacetimedb-core.workspace = true
cargo_metadata.workspace = true
assert_cmd = "2"
predicates = "3"
Expand Down
36 changes: 36 additions & 0 deletions crates/smoketests/tests/smoketests/change_host_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use spacetimedb::messages::control_db::HostType;
use spacetimedb_smoketests::{require_local_server, require_pnpm, Smoketest};

const TS_MODULE_BASIC: &str = r#"import { schema, t, table } from "spacetimedb/server";
Expand Down Expand Up @@ -83,3 +84,38 @@ fn assert_has_rows(test: &Smoketest, names: &[&str], context: &str) {
"{context}: expected all of {names:?} to be in result: {output}"
)
}

/// Tests that a legacy database that has a wrong host type in `st_module` is
/// auto-repaired upon startup.
///
/// NOTE: The repair mechanism shall be removed eventually, and so shall this
/// test (which will fail when the mechanism is sunset).
///
/// This test restarts the server.
#[test]
fn test_repair_host_type() {
require_pnpm!();
require_local_server!();

let mut test = Smoketest::builder().autopublish(false).build();

test.publish_typescript_module_source("modules-basic-ts", "basic-ts", TS_MODULE_BASIC)
.unwrap();
assert_host_type(&test, HostType::Js);
// Set the program kind to the wrong value.
test.sql_confirmed("update st_module set program_kind=0").unwrap();
assert_host_type(&test, HostType::Wasm);

// After restarting, the database both comes up and has the right host type.
test.restart_server();
assert_host_type(&test, HostType::Js);
}

fn assert_host_type(test: &Smoketest, host_type: HostType) {
let output = test.sql_confirmed("select program_kind from st_module").unwrap();
let rows = output.lines().skip(2).map(|s| s.trim()).collect::<Vec<_>>();
match host_type {
HostType::Wasm => assert_eq!(&rows, &["0"]),
HostType::Js => assert_eq!(&rows, &["1"]),
}
}
Loading