Minor lib improvements (#4261)
This commit is contained in:
parent
a701230e9d
commit
07a88383fa
22 changed files with 1323 additions and 1146 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -2177,19 +2177,6 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-concurrency"
|
|
||||||
version = "7.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b590a729e1cbaf9ae3ec294143ea034d93cbb1de01c884d04bcd0af8b613d02"
|
|
||||||
dependencies = [
|
|
||||||
"bitvec",
|
|
||||||
"futures-core",
|
|
||||||
"pin-project",
|
|
||||||
"slab",
|
|
||||||
"smallvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -5952,7 +5939,6 @@ dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"flume",
|
"flume",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-concurrency",
|
|
||||||
"geo 0.27.0",
|
"geo 0.27.0",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
|
|
|
@ -10,13 +10,11 @@ args = ["check", "--locked", "--workspace"]
|
||||||
[tasks.ci-check-wasm]
|
[tasks.ci-check-wasm]
|
||||||
category = "CI - CHECK"
|
category = "CI - CHECK"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["check", "--locked", "--package", "surrealdb", "--features", "protocol-ws,protocol-http,kv-mem,kv-indxdb,http,jwks", "--target", "wasm32-unknown-unknown"]
|
args = ["check", "--locked", "--package", "surrealdb", "--features", "protocol-ws,protocol-http,kv-mem,kv-indxdb,http,jwks", "--target", "wasm32-unknown-unknown"]
|
||||||
|
|
||||||
[tasks.ci-clippy]
|
[tasks.ci-clippy]
|
||||||
category = "CI - CHECK"
|
category = "CI - CHECK"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["clippy", "--all-targets", "--features", "storage-mem,storage-rocksdb,storage-tikv,storage-fdb,scripting,http,jwks", "--tests", "--benches", "--examples", "--bins", "--", "-D", "warnings"]
|
args = ["clippy", "--all-targets", "--features", "storage-mem,storage-rocksdb,storage-tikv,storage-fdb,scripting,http,jwks", "--tests", "--benches", "--examples", "--bins", "--", "-D", "warnings"]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -26,31 +24,43 @@ args = ["clippy", "--all-targets", "--features", "storage-mem,storage-rocksdb,st
|
||||||
[tasks.ci-cli-integration]
|
[tasks.ci-cli-integration]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable", RUST_LOG = { value = "cli_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "cli_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
||||||
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem,storage-surrealkv,http,scripting,jwks", "--workspace", "--test", "cli_integration", "--", "cli_integration"]
|
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem,storage-surrealkv,http,scripting,jwks", "--workspace", "--test", "cli_integration", "--", "cli_integration"]
|
||||||
|
|
||||||
[tasks.ci-http-integration]
|
[tasks.ci-http-integration]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable", RUST_LOG = { value = "http_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "http_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
||||||
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem,http-compression,jwks", "--workspace", "--test", "http_integration", "--", "http_integration"]
|
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem,http-compression,jwks", "--workspace", "--test", "http_integration", "--", "http_integration"]
|
||||||
|
|
||||||
[tasks.ci-ws-integration]
|
[tasks.ci-ws-integration]
|
||||||
category = "WS - INTEGRATION TESTS"
|
category = "WS - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable", RUST_LOG = { value = "ws_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "ws_integration=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
||||||
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem", "--workspace", "--test", "ws_integration", "--", "ws_integration"]
|
args = ["test", "--locked", "--no-default-features", "--features", "storage-mem", "--workspace", "--test", "ws_integration", "--", "ws_integration"]
|
||||||
|
|
||||||
[tasks.ci-ml-integration]
|
[tasks.ci-ml-integration]
|
||||||
category = "ML - INTEGRATION TESTS"
|
category = "ML - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable", RUST_LOG = { value = "cli_integration::common=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "cli_integration::common=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
||||||
args = ["test", "--locked", "--features", "storage-mem,ml", "--workspace", "--test", "ml_integration", "--", "ml_integration", "--nocapture"]
|
args = ["test", "--locked", "--features", "storage-mem,ml", "--workspace", "--test", "ml_integration", "--", "ml_integration", "--nocapture"]
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.ci-test-workspace]
|
||||||
|
category = "CI - INTEGRATION TESTS"
|
||||||
|
command = "cargo"
|
||||||
|
args = [
|
||||||
|
"test", "--locked", "--no-default-features", "--features", "storage-mem,scripting,http,jwks", "--workspace", "--",
|
||||||
|
"--skip", "api_integration",
|
||||||
|
"--skip", "cli_integration",
|
||||||
|
"--skip", "http_integration",
|
||||||
|
"--skip", "ws_integration",
|
||||||
|
"--skip", "database_upgrade"
|
||||||
|
]
|
||||||
|
|
||||||
[tasks.ci-workspace-coverage]
|
[tasks.ci-workspace-coverage]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = [
|
args = [
|
||||||
"llvm-cov", "--html", "--locked", "--no-default-features", "--features", "storage-mem,scripting,http,jwks", "--workspace", "--",
|
"llvm-cov", "--html", "--locked", "--no-default-features", "--features", "storage-mem,scripting,http,jwks", "--workspace", "--",
|
||||||
"--skip", "api_integration",
|
"--skip", "api_integration",
|
||||||
|
@ -63,7 +73,6 @@ args = [
|
||||||
[tasks.test-workspace-coverage-complete]
|
[tasks.test-workspace-coverage-complete]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["llvm-cov", "--html", "--locked", "--no-default-features", "--features", "protocol-ws,protocol-http,kv-mem,kv-rocksdb", "--workspace"]
|
args = ["llvm-cov", "--html", "--locked", "--no-default-features", "--features", "protocol-ws,protocol-http,kv-mem,kv-rocksdb", "--workspace"]
|
||||||
|
|
||||||
[tasks.ci-workspace-coverage-complete]
|
[tasks.ci-workspace-coverage-complete]
|
||||||
|
@ -83,25 +92,25 @@ run_task = { name = ["test-database-upgrade"], fork = true }
|
||||||
[tasks.test-kvs]
|
[tasks.test-kvs]
|
||||||
private = true
|
private = true
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { RUST_BACKTRACE = 1 }
|
||||||
args = ["test", "--locked", "--package", "surrealdb", "--no-default-features", "--features", "${_TEST_FEATURES}", "--lib", "kvs"]
|
args = ["test", "--locked", "--package", "surrealdb", "--no-default-features", "--features", "${_TEST_FEATURES}", "--lib", "kvs"]
|
||||||
|
|
||||||
|
|
||||||
[tasks.test-api-integration]
|
[tasks.test-api-integration]
|
||||||
private = true
|
private = true
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { RUST_BACKTRACE = 1 }
|
||||||
args = ["test", "--locked", "--package", "surrealdb", "--no-default-features", "--features", "${_TEST_FEATURES}", "--test", "api", "api_integration::${_TEST_API_ENGINE}"]
|
args = ["test", "--locked", "--package", "surrealdb", "--no-default-features", "--features", "${_TEST_FEATURES}", "--test", "api", "api_integration::${_TEST_API_ENGINE}"]
|
||||||
|
|
||||||
[tasks.ci-api-integration]
|
[tasks.ci-api-integration]
|
||||||
env = { RUST_BACKTRACE = 1, _START_SURREALDB_PATH = "memory", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { RUST_BACKTRACE = 1, _START_SURREALDB_PATH = "memory" }
|
||||||
private = true
|
private = true
|
||||||
run_task = { name = ["start-surrealdb", "test-api-integration", "stop-surrealdb"], fork = true }
|
run_task = { name = ["start-surrealdb", "test-api-integration", "stop-surrealdb"], fork = true }
|
||||||
|
|
||||||
[tasks.test-database-upgrade]
|
[tasks.test-database-upgrade]
|
||||||
private = true
|
private = true
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUST_BACKTRACE = 1, RUST_LOG = "info", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { RUST_BACKTRACE = 1, RUST_LOG = "info" }
|
||||||
args = ["test", "--locked", "--no-default-features", "--features", "${_TEST_FEATURES}", "--workspace", "--test", "database_upgrade", "--", "database_upgrade", "--show-output"]
|
args = ["test", "--locked", "--no-default-features", "--features", "${_TEST_FEATURES}", "--workspace", "--test", "database_upgrade", "--", "database_upgrade", "--show-output"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,17 +120,17 @@ args = ["test", "--locked", "--no-default-features", "--features", "${_TEST_FEAT
|
||||||
|
|
||||||
[tasks.ci-api-integration-http]
|
[tasks.ci-api-integration-http]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
env = { _TEST_API_ENGINE = "http", _TEST_FEATURES = "protocol-http", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { _TEST_API_ENGINE = "http", _TEST_FEATURES = "protocol-http" }
|
||||||
run_task = "ci-api-integration"
|
run_task = "ci-api-integration"
|
||||||
|
|
||||||
[tasks.ci-api-integration-ws]
|
[tasks.ci-api-integration-ws]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
env = { _TEST_API_ENGINE = "ws", _TEST_FEATURES = "protocol-ws", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { _TEST_API_ENGINE = "ws", _TEST_FEATURES = "protocol-ws" }
|
||||||
run_task = "ci-api-integration"
|
run_task = "ci-api-integration"
|
||||||
|
|
||||||
[tasks.ci-api-integration-any]
|
[tasks.ci-api-integration-any]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
env = { _TEST_API_ENGINE = "any", _TEST_FEATURES = "protocol-http", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
env = { _TEST_API_ENGINE = "any", _TEST_FEATURES = "protocol-http" }
|
||||||
run_task = "ci-api-integration"
|
run_task = "ci-api-integration"
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -256,7 +265,6 @@ ${HOME}/.tiup/bin/tiup clean --all
|
||||||
[tasks.build-surrealdb]
|
[tasks.build-surrealdb]
|
||||||
category = "CI - BUILD"
|
category = "CI - BUILD"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["build", "--locked", "--no-default-features", "--features", "storage-mem"]
|
args = ["build", "--locked", "--no-default-features", "--features", "storage-mem"]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -265,7 +273,6 @@ args = ["build", "--locked", "--no-default-features", "--features", "storage-mem
|
||||||
[tasks.ci-bench]
|
[tasks.ci-bench]
|
||||||
category = "CI - BENCHMARK"
|
category = "CI - BENCHMARK"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["bench", "--quiet", "--package", "surrealdb", "--no-default-features", "--features", "kv-mem,scripting,http,jwks", "${@}"]
|
args = ["bench", "--quiet", "--package", "surrealdb", "--no-default-features", "--features", "kv-mem,scripting,http,jwks", "${@}"]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -277,13 +284,11 @@ BENCH_NUM_OPS = { value = "1000", condition = { env_not_set = ["BENCH_NUM_OPS"]
|
||||||
BENCH_DURATION = { value = "30", condition = { env_not_set = ["BENCH_DURATION"] } }
|
BENCH_DURATION = { value = "30", condition = { env_not_set = ["BENCH_DURATION"] } }
|
||||||
BENCH_SAMPLE_SIZE = { value = "10", condition = { env_not_set = ["BENCH_SAMPLE_SIZE"] } }
|
BENCH_SAMPLE_SIZE = { value = "10", condition = { env_not_set = ["BENCH_SAMPLE_SIZE"] } }
|
||||||
BENCH_FEATURES = { value = "protocol-ws,kv-mem,kv-rocksdb,kv-fdb-7_1,kv-surrealkv", condition = { env_not_set = ["BENCH_FEATURES"] } }
|
BENCH_FEATURES = { value = "protocol-ws,kv-mem,kv-rocksdb,kv-fdb-7_1,kv-surrealkv", condition = { env_not_set = ["BENCH_FEATURES"] } }
|
||||||
RUSTFLAGS = "--cfg surrealdb_unstable"
|
|
||||||
|
|
||||||
[tasks.bench-target]
|
[tasks.bench-target]
|
||||||
private = true
|
private = true
|
||||||
category = "CI - BENCHMARK - SurrealDB Target"
|
category = "CI - BENCHMARK - SurrealDB Target"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["bench", "--package", "surrealdb", "--bench", "sdb", "--no-default-features", "--features", "${BENCH_FEATURES}", "${@}"]
|
args = ["bench", "--package", "surrealdb", "--bench", "sdb", "--no-default-features", "--features", "${BENCH_FEATURES}", "${@}"]
|
||||||
|
|
||||||
[tasks.bench-lib-mem]
|
[tasks.bench-lib-mem]
|
||||||
|
|
|
@ -17,21 +17,19 @@ dependencies = ["cargo-upgrade", "cargo-update"]
|
||||||
[tasks.docs]
|
[tasks.docs]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTDOCFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["doc", "--open", "--no-deps", "--package", "surrealdb", "--features", "rustls,native-tls,protocol-ws,protocol-http,kv-mem,kv-rocksdb,kv-tikv,http,scripting,jwks"]
|
args = ["doc", "--open", "--no-deps", "--package", "surrealdb", "--features", "rustls,native-tls,protocol-ws,protocol-http,kv-mem,kv-rocksdb,kv-tikv,http,scripting,jwks"]
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable", RUSTDOCFLAGS = "--cfg surrealdb_unstable", RUST_MIN_STACK={ value = "4194304", condition = { env_not_set = ["RUST_MIN_STACK"] } } }
|
env = { RUST_MIN_STACK={ value = "4194304", condition = { env_not_set = ["RUST_MIN_STACK"] } } }
|
||||||
args = ["test", "--workspace", "--no-fail-fast"]
|
args = ["test", "--workspace", "--no-fail-fast"]
|
||||||
|
|
||||||
# Check
|
# Check
|
||||||
[tasks.cargo-check]
|
[tasks.cargo-check]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["check", "--workspace", "--features", "${DEV_FEATURES}"]
|
args = ["check", "--workspace", "--features", "${DEV_FEATURES}"]
|
||||||
|
|
||||||
[tasks.cargo-fmt]
|
[tasks.cargo-fmt]
|
||||||
|
@ -50,7 +48,6 @@ script = """
|
||||||
[tasks.cargo-clippy]
|
[tasks.cargo-clippy]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"]
|
args = ["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"]
|
||||||
|
|
||||||
[tasks.check]
|
[tasks.check]
|
||||||
|
@ -71,28 +68,24 @@ args = ["clean"]
|
||||||
[tasks.bench]
|
[tasks.bench]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["bench", "--package", "surrealdb", "--no-default-features", "--features", "kv-mem,http,scripting,jwks", "--", "${@}"]
|
args = ["bench", "--package", "surrealdb", "--no-default-features", "--features", "kv-mem,http,scripting,jwks", "--", "${@}"]
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
[tasks.run]
|
[tasks.run]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "${@}"]
|
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "${@}"]
|
||||||
|
|
||||||
# Serve
|
# Serve
|
||||||
[tasks.serve]
|
[tasks.serve]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "start", "--allow-all", "${@}"]
|
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "start", "--allow-all", "${@}"]
|
||||||
|
|
||||||
# SQL
|
# SQL
|
||||||
[tasks.sql]
|
[tasks.sql]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "sql", "--pretty", "${@}"]
|
args = ["run", "--no-default-features", "--features", "${DEV_FEATURES}", "--", "sql", "--pretty", "${@}"]
|
||||||
|
|
||||||
# Quick
|
# Quick
|
||||||
|
@ -105,7 +98,6 @@ args = ["build", "${@}"]
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
category = "LOCAL USAGE"
|
category = "LOCAL USAGE"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
env = { RUSTFLAGS = "--cfg surrealdb_unstable" }
|
|
||||||
args = ["build", "--release", "${@}"]
|
args = ["build", "--release", "${@}"]
|
||||||
|
|
||||||
# Default
|
# Default
|
||||||
|
|
|
@ -16,7 +16,9 @@ use crate::sql::range::Range;
|
||||||
use crate::sql::table::Table;
|
use crate::sql::table::Table;
|
||||||
use crate::sql::thing::Thing;
|
use crate::sql::thing::Thing;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use reblessive::{tree::Stk, TreeStack};
|
use reblessive::tree::Stk;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use reblessive::TreeStack;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub static ID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]);
|
||||||
pub static METHOD: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("method")]);
|
pub static METHOD: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("method")]);
|
||||||
pub static PARAMS: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("params")]);
|
pub static PARAMS: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("params")]);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub id: Option<Value>,
|
pub id: Option<Value>,
|
||||||
pub method: String,
|
pub method: String,
|
||||||
|
|
|
@ -79,7 +79,6 @@ chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
dmp = "0.2.0"
|
dmp = "0.2.0"
|
||||||
flume = "0.11.0"
|
flume = "0.11.0"
|
||||||
futures = "0.3.29"
|
futures = "0.3.29"
|
||||||
futures-concurrency = "7.4.3"
|
|
||||||
geo = { version = "0.27.0", features = ["use-serde"] }
|
geo = { version = "0.27.0", features = ["use-serde"] }
|
||||||
indexmap = { version = "2.1.0", features = ["serde"] }
|
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||||
native-tls = { version = "0.2.11", optional = true }
|
native-tls = { version = "0.2.11", optional = true }
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub(crate) struct Route {
|
||||||
/// Message router
|
/// Message router
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Router {
|
pub struct Router {
|
||||||
pub(crate) sender: Sender<Option<Route>>,
|
pub(crate) sender: Sender<Route>,
|
||||||
pub(crate) last_id: AtomicI64,
|
pub(crate) last_id: AtomicI64,
|
||||||
pub(crate) features: HashSet<ExtraFeatures>,
|
pub(crate) features: HashSet<ExtraFeatures>,
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,6 @@ impl Router {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Router {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let _res = self.sender.send(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The query method
|
/// The query method
|
||||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
|
|
@ -68,7 +68,7 @@ impl Connection for Any {
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::native::router(address, conn_tx, route_rx);
|
tokio::spawn(engine::local::native::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??
|
conn_rx.into_recv_async().await??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ impl Connection for Any {
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::native::router(address, conn_tx, route_rx);
|
tokio::spawn(engine::local::native::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??
|
conn_rx.into_recv_async().await??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ impl Connection for Any {
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::native::router(address, conn_tx, route_rx);
|
tokio::spawn(engine::local::native::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??
|
conn_rx.into_recv_async().await??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ impl Connection for Any {
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::native::router(address, conn_tx, route_rx);
|
tokio::spawn(engine::local::native::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??
|
conn_rx.into_recv_async().await??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ impl Connection for Any {
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::native::router(address, conn_tx, route_rx);
|
tokio::spawn(engine::local::native::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??
|
conn_rx.into_recv_async().await??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,9 @@ impl Connection for Any {
|
||||||
client.get(base_url.join(Method::Health.as_str())?),
|
client.get(base_url.join(Method::Health.as_str())?),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
engine::remote::http::native::router(base_url, client, route_rx);
|
tokio::spawn(engine::remote::http::native::run_router(
|
||||||
|
base_url, client, route_rx,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "protocol-http"))]
|
#[cfg(not(feature = "protocol-http"))]
|
||||||
|
@ -195,14 +197,14 @@ impl Connection for Any {
|
||||||
maybe_connector.clone(),
|
maybe_connector.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
engine::remote::ws::native::router(
|
tokio::spawn(engine::remote::ws::native::run_router(
|
||||||
endpoint,
|
endpoint,
|
||||||
maybe_connector,
|
maybe_connector,
|
||||||
capacity,
|
capacity,
|
||||||
config,
|
config,
|
||||||
socket,
|
socket,
|
||||||
route_rx,
|
route_rx,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "protocol-ws"))]
|
#[cfg(not(feature = "protocol-ws"))]
|
||||||
|
@ -237,7 +239,7 @@ impl Connection for Any {
|
||||||
request: (self.id, self.method, param),
|
request: (self.id, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,12 @@ use crate::opt::WaitFor;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicI64;
|
use std::sync::atomic::AtomicI64;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
|
||||||
impl crate::api::Connection for Any {}
|
impl crate::api::Connection for Any {}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-fdb")]
|
#[cfg(feature = "kv-fdb")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-indxdb")]
|
#[cfg(feature = "kv-indxdb")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-mem")]
|
#[cfg(feature = "kv-mem")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-rocksdb")]
|
#[cfg(feature = "kv-rocksdb")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-surrealkv")]
|
#[cfg(feature = "kv-surrealkv")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ impl Connection for Any {
|
||||||
#[cfg(feature = "kv-tikv")]
|
#[cfg(feature = "kv-tikv")]
|
||||||
{
|
{
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
engine::local::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::local::wasm::run_router(address, conn_tx, route_rx));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,9 @@ impl Connection for Any {
|
||||||
EndpointKind::Http | EndpointKind::Https => {
|
EndpointKind::Http | EndpointKind::Https => {
|
||||||
#[cfg(feature = "protocol-http")]
|
#[cfg(feature = "protocol-http")]
|
||||||
{
|
{
|
||||||
engine::remote::http::wasm::router(address, conn_tx, route_rx);
|
spawn_local(engine::remote::http::wasm::run_router(
|
||||||
|
address, conn_tx, route_rx,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "protocol-http"))]
|
#[cfg(not(feature = "protocol-http"))]
|
||||||
|
@ -155,7 +157,9 @@ impl Connection for Any {
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
let mut endpoint = address;
|
let mut endpoint = address;
|
||||||
endpoint.url = endpoint.url.join(engine::remote::ws::PATH)?;
|
endpoint.url = endpoint.url.join(engine::remote::ws::PATH)?;
|
||||||
engine::remote::ws::wasm::router(endpoint, capacity, conn_tx, route_rx);
|
spawn_local(engine::remote::ws::wasm::run_router(
|
||||||
|
endpoint, capacity, conn_tx, route_rx,
|
||||||
|
));
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +196,7 @@ impl Connection for Any {
|
||||||
request: (self.id, self.method, param),
|
request: (self.id, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,8 @@ use crate::opt::WaitFor;
|
||||||
use crate::options::EngineOptions;
|
use crate::options::EngineOptions;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use futures::future::Either;
|
|
||||||
use futures::stream::poll_fn;
|
use futures::stream::poll_fn;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures_concurrency::stream::Merge as _;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
@ -55,7 +53,7 @@ impl Connection for Db {
|
||||||
|
|
||||||
let (conn_tx, conn_rx) = flume::bounded(1);
|
let (conn_tx, conn_rx) = flume::bounded(1);
|
||||||
|
|
||||||
router(address, conn_tx, route_rx);
|
tokio::spawn(run_router(address, conn_tx, route_rx));
|
||||||
|
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
|
|
||||||
|
@ -85,18 +83,17 @@ impl Connection for Db {
|
||||||
request: (0, self.method, param),
|
request: (0, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn router(
|
pub(crate) async fn run_router(
|
||||||
address: Endpoint,
|
address: Endpoint,
|
||||||
conn_tx: Sender<Result<()>>,
|
conn_tx: Sender<Result<()>>,
|
||||||
route_rx: Receiver<Option<Route>>,
|
route_rx: Receiver<Route>,
|
||||||
) {
|
) {
|
||||||
tokio::spawn(async move {
|
|
||||||
let configured_root = match address.config.auth {
|
let configured_root = match address.config.auth {
|
||||||
Level::Root => Some(Root {
|
Level::Root => Some(Root {
|
||||||
username: &address.config.username,
|
username: &address.config.username,
|
||||||
|
@ -118,8 +115,7 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
// If a root user is specified, setup the initial datastore credentials
|
// If a root user is specified, setup the initial datastore credentials
|
||||||
if let Some(root) = configured_root {
|
if let Some(root) = configured_root {
|
||||||
if let Err(error) = kvs.setup_initial_creds(root.username, root.password).await
|
if let Err(error) = kvs.setup_initial_creds(root.username, root.password).await {
|
||||||
{
|
|
||||||
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -172,25 +168,21 @@ pub(crate) fn router(
|
||||||
let (tasks, task_chans) = start_tasks(&opt, kvs.clone());
|
let (tasks, task_chans) = start_tasks(&opt, kvs.clone());
|
||||||
|
|
||||||
let mut notifications = kvs.notifications();
|
let mut notifications = kvs.notifications();
|
||||||
let notification_stream = poll_fn(move |cx| match &mut notifications {
|
let mut notification_stream = poll_fn(move |cx| match &mut notifications {
|
||||||
Some(rx) => rx.poll_next_unpin(cx),
|
Some(rx) => rx.poll_next_unpin(cx),
|
||||||
None => Poll::Ready(None),
|
// return poll pending so that this future is never woken up again and therefore not
|
||||||
|
// constantly polled.
|
||||||
|
None => Poll::Pending,
|
||||||
});
|
});
|
||||||
|
let mut route_stream = route_rx.into_stream();
|
||||||
|
|
||||||
let streams = (route_rx.stream().map(Either::Left), notification_stream.map(Either::Right));
|
loop {
|
||||||
let mut merged = streams.merge();
|
tokio::select! {
|
||||||
|
route = route_stream.next() => {
|
||||||
while let Some(either) = merged.next().await {
|
let Some(route) = route else {
|
||||||
match either {
|
break
|
||||||
Either::Left(None) => break, // Received a shutdown signal
|
};
|
||||||
Either::Left(Some(route)) => {
|
match super::router(route.request, &kvs, &mut session, &mut vars, &mut live_queries)
|
||||||
match super::router(
|
|
||||||
route.request,
|
|
||||||
&kvs,
|
|
||||||
&mut session,
|
|
||||||
&mut vars,
|
|
||||||
&mut live_queries,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
|
@ -201,7 +193,12 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Either::Right(notification) => {
|
notification = notification_stream.next() => {
|
||||||
|
let Some(notification) = notification else {
|
||||||
|
// TODO: Maybe we should do something more then ignore a closed notifications
|
||||||
|
// channel?
|
||||||
|
continue
|
||||||
|
};
|
||||||
let id = notification.id;
|
let id = notification.id;
|
||||||
if let Some(sender) = live_queries.get(&id) {
|
if let Some(sender) = live_queries.get(&id) {
|
||||||
if sender.send(notification).await.is_err() {
|
if sender.send(notification).await.is_err() {
|
||||||
|
@ -219,10 +216,9 @@ pub(crate) fn router(
|
||||||
|
|
||||||
// Stop maintenance tasks
|
// Stop maintenance tasks
|
||||||
for chan in task_chans {
|
for chan in task_chans {
|
||||||
if let Err(_empty_tuple) = chan.send(()) {
|
if chan.send(()).is_err() {
|
||||||
error!("Error sending shutdown signal to task");
|
error!("Error sending shutdown signal to task");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tasks.resolve().await.unwrap();
|
tasks.resolve().await.unwrap();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,13 @@ use crate::opt::WaitFor;
|
||||||
use crate::options::EngineOptions;
|
use crate::options::EngineOptions;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use futures::future::Either;
|
|
||||||
use futures::stream::poll_fn;
|
use futures::stream::poll_fn;
|
||||||
|
use futures::FutureExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures_concurrency::stream::Merge as _;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicI64;
|
use std::sync::atomic::AtomicI64;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -58,7 +56,7 @@ impl Connection for Db {
|
||||||
|
|
||||||
let (conn_tx, conn_rx) = flume::bounded(1);
|
let (conn_tx, conn_rx) = flume::bounded(1);
|
||||||
|
|
||||||
router(address, conn_tx, route_rx);
|
spawn_local(run_router(address, conn_tx, route_rx));
|
||||||
|
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
|
|
||||||
|
@ -87,18 +85,17 @@ impl Connection for Db {
|
||||||
request: (0, self.method, param),
|
request: (0, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn router(
|
pub(crate) async fn run_router(
|
||||||
address: Endpoint,
|
address: Endpoint,
|
||||||
conn_tx: Sender<Result<()>>,
|
conn_tx: Sender<Result<()>>,
|
||||||
route_rx: Receiver<Option<Route>>,
|
route_rx: Receiver<Route>,
|
||||||
) {
|
) {
|
||||||
spawn_local(async move {
|
|
||||||
let configured_root = match address.config.auth {
|
let configured_root = match address.config.auth {
|
||||||
Level::Root => Some(Root {
|
Level::Root => Some(Root {
|
||||||
username: &address.config.username,
|
username: &address.config.username,
|
||||||
|
@ -115,8 +112,7 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
// If a root user is specified, setup the initial datastore credentials
|
// If a root user is specified, setup the initial datastore credentials
|
||||||
if let Some(root) = configured_root {
|
if let Some(root) = configured_root {
|
||||||
if let Err(error) = kvs.setup_initial_creds(root.username, root.password).await
|
if let Err(error) = kvs.setup_initial_creds(root.username, root.password).await {
|
||||||
{
|
|
||||||
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -151,18 +147,22 @@ pub(crate) fn router(
|
||||||
let (_tasks, task_chans) = start_tasks(&opt, kvs.clone());
|
let (_tasks, task_chans) = start_tasks(&opt, kvs.clone());
|
||||||
|
|
||||||
let mut notifications = kvs.notifications();
|
let mut notifications = kvs.notifications();
|
||||||
let notification_stream = poll_fn(move |cx| match &mut notifications {
|
let mut notification_stream = poll_fn(move |cx| match &mut notifications {
|
||||||
Some(rx) => rx.poll_next_unpin(cx),
|
Some(rx) => rx.poll_next_unpin(cx),
|
||||||
None => Poll::Ready(None),
|
None => Poll::Pending,
|
||||||
});
|
});
|
||||||
|
|
||||||
let streams = (route_rx.stream().map(Either::Left), notification_stream.map(Either::Right));
|
let mut route_stream = route_rx.into_stream();
|
||||||
let mut merged = streams.merge();
|
|
||||||
|
loop {
|
||||||
|
// use the less ergonomic futures::select as tokio::select is not available.
|
||||||
|
futures::select! {
|
||||||
|
route = route_stream.next().fuse() => {
|
||||||
|
let Some(route) = route else {
|
||||||
|
// termination requested
|
||||||
|
break
|
||||||
|
};
|
||||||
|
|
||||||
while let Some(either) = merged.next().await {
|
|
||||||
match either {
|
|
||||||
Either::Left(None) => break, // Received a shutdown signal
|
|
||||||
Either::Left(Some(route)) => {
|
|
||||||
match super::router(
|
match super::router(
|
||||||
route.request,
|
route.request,
|
||||||
&kvs,
|
&kvs,
|
||||||
|
@ -180,7 +180,13 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Either::Right(notification) => {
|
notification = notification_stream.next().fuse() => {
|
||||||
|
let Some(notification) = notification else {
|
||||||
|
// TODO: maybe do something else then ignore a disconnected notification
|
||||||
|
// channel.
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let id = notification.id;
|
let id = notification.id;
|
||||||
if let Some(sender) = live_queries.get(&id) {
|
if let Some(sender) = live_queries.get(&id) {
|
||||||
if sender.send(notification).await.is_err() {
|
if sender.send(notification).await.is_err() {
|
||||||
|
@ -198,9 +204,8 @@ pub(crate) fn router(
|
||||||
|
|
||||||
// Stop maintenance tasks
|
// Stop maintenance tasks
|
||||||
for chan in task_chans {
|
for chan in task_chans {
|
||||||
if let Err(_empty_tuple) = chan.send(()) {
|
if chan.send(()).is_err() {
|
||||||
error!("Error sending shutdown signal to maintenance task");
|
error!("Error sending shutdown signal to maintenance task");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl Connection for Client {
|
||||||
capacity => flume::bounded(capacity),
|
capacity => flume::bounded(capacity),
|
||||||
};
|
};
|
||||||
|
|
||||||
router(base_url, client, route_rx);
|
tokio::spawn(run_router(base_url, client, route_rx));
|
||||||
|
|
||||||
let mut features = HashSet::new();
|
let mut features = HashSet::new();
|
||||||
features.insert(ExtraFeatures::Backup);
|
features.insert(ExtraFeatures::Backup);
|
||||||
|
@ -94,30 +94,22 @@ impl Connection for Client {
|
||||||
request: (0, self.method, param),
|
request: (0, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn router(base_url: Url, client: reqwest::Client, route_rx: Receiver<Option<Route>>) {
|
pub(crate) async fn run_router(base_url: Url, client: reqwest::Client, route_rx: Receiver<Route>) {
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
let mut vars = IndexMap::new();
|
let mut vars = IndexMap::new();
|
||||||
let mut auth = None;
|
let mut auth = None;
|
||||||
let mut stream = route_rx.into_stream();
|
let mut stream = route_rx.into_stream();
|
||||||
|
|
||||||
while let Some(Some(route)) = stream.next().await {
|
while let Some(route) = stream.next().await {
|
||||||
let result = super::router(
|
let result =
|
||||||
route.request,
|
super::router(route.request, &base_url, &client, &mut headers, &mut vars, &mut auth)
|
||||||
&base_url,
|
|
||||||
&client,
|
|
||||||
&mut headers,
|
|
||||||
&mut vars,
|
|
||||||
&mut auth,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
let _ = route.response.into_send_async(result).await;
|
let _ = route.response.into_send_async(result).await;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ use reqwest::header::HeaderMap;
|
||||||
use reqwest::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicI64;
|
use std::sync::atomic::AtomicI64;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -48,7 +47,7 @@ impl Connection for Client {
|
||||||
|
|
||||||
let (conn_tx, conn_rx) = flume::bounded(1);
|
let (conn_tx, conn_rx) = flume::bounded(1);
|
||||||
|
|
||||||
router(address, conn_tx, route_rx);
|
spawn_local(run_router(address, conn_tx, route_rx));
|
||||||
|
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@ impl Connection for Client {
|
||||||
request: (0, self.method, param),
|
request: (0, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -90,12 +89,11 @@ async fn client(base_url: &Url) -> Result<reqwest::Client> {
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn router(
|
pub(crate) async fn run_router(
|
||||||
address: Endpoint,
|
address: Endpoint,
|
||||||
conn_tx: Sender<Result<()>>,
|
conn_tx: Sender<Result<()>>,
|
||||||
route_rx: Receiver<Option<Route>>,
|
route_rx: Receiver<Route>,
|
||||||
) {
|
) {
|
||||||
spawn_local(async move {
|
|
||||||
let base_url = address.url;
|
let base_url = address.url;
|
||||||
|
|
||||||
let client = match client(&base_url).await {
|
let client = match client(&base_url).await {
|
||||||
|
@ -114,15 +112,8 @@ pub(crate) fn router(
|
||||||
let mut auth = None;
|
let mut auth = None;
|
||||||
let mut stream = route_rx.into_stream();
|
let mut stream = route_rx.into_stream();
|
||||||
|
|
||||||
while let Some(Some(route)) = stream.next().await {
|
while let Some(route) = stream.next().await {
|
||||||
match super::router(
|
match super::router(route.request, &base_url, &client, &mut headers, &mut vars, &mut auth)
|
||||||
route.request,
|
|
||||||
&base_url,
|
|
||||||
&client,
|
|
||||||
&mut headers,
|
|
||||||
&mut vars,
|
|
||||||
&mut auth,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
|
@ -133,5 +124,4 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,20 +20,160 @@ use crate::dbs::Status;
|
||||||
use crate::method::Stats;
|
use crate::method::Stats;
|
||||||
use crate::opt::IntoEndpoint;
|
use crate::opt::IntoEndpoint;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
|
use bincode::Options as _;
|
||||||
|
use flume::Sender;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use revision::Revisioned;
|
use revision::Revisioned;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::ser::SerializeMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use surrealdb_core::dbs::Notification as CoreNotification;
|
||||||
|
use trice::Instant;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(crate) const PATH: &str = "rpc";
|
pub(crate) const PATH: &str = "rpc";
|
||||||
const PING_INTERVAL: Duration = Duration::from_secs(5);
|
const PING_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
const PING_METHOD: &str = "ping";
|
const PING_METHOD: &str = "ping";
|
||||||
const REVISION_HEADER: &str = "revision";
|
const REVISION_HEADER: &str = "revision";
|
||||||
|
|
||||||
|
/// A struct which will be serialized as a map to behave like the previously used BTreeMap.
|
||||||
|
///
|
||||||
|
/// This struct serializes as if it is a surrealdb_core::sql::Value::Object.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RouterRequest {
|
||||||
|
id: Option<Value>,
|
||||||
|
method: Value,
|
||||||
|
params: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for RouterRequest {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
struct InnerRequest<'a>(&'a RouterRequest);
|
||||||
|
|
||||||
|
impl Serialize for InnerRequest<'_> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let size = 1 + self.0.id.is_some() as usize + self.0.params.is_some() as usize;
|
||||||
|
let mut map = serializer.serialize_map(Some(size))?;
|
||||||
|
if let Some(id) = self.0.id.as_ref() {
|
||||||
|
map.serialize_entry("id", id)?;
|
||||||
|
}
|
||||||
|
map.serialize_entry("method", &self.0.method)?;
|
||||||
|
if let Some(params) = self.0.params.as_ref() {
|
||||||
|
map.serialize_entry("params", params)?;
|
||||||
|
}
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer.serialize_newtype_variant("Value", 9, "Object", &InnerRequest(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Revisioned for RouterRequest {
|
||||||
|
fn revision() -> u16 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_revisioned<W: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
w: &mut W,
|
||||||
|
) -> std::result::Result<(), revision::Error> {
|
||||||
|
// version
|
||||||
|
Revisioned::serialize_revisioned(&1u32, w)?;
|
||||||
|
// object variant
|
||||||
|
Revisioned::serialize_revisioned(&9u32, w)?;
|
||||||
|
// object wrapper version
|
||||||
|
Revisioned::serialize_revisioned(&1u32, w)?;
|
||||||
|
|
||||||
|
let size = 1 + self.id.is_some() as usize + self.params.is_some() as usize;
|
||||||
|
size.serialize_revisioned(w)?;
|
||||||
|
|
||||||
|
let serializer = bincode::options()
|
||||||
|
.with_no_limit()
|
||||||
|
.with_little_endian()
|
||||||
|
.with_varint_encoding()
|
||||||
|
.reject_trailing_bytes();
|
||||||
|
|
||||||
|
if let Some(x) = self.id.as_ref() {
|
||||||
|
serializer
|
||||||
|
.serialize_into(&mut *w, "id")
|
||||||
|
.map_err(|err| revision::Error::Serialize(err.to_string()))?;
|
||||||
|
x.serialize_revisioned(w)?;
|
||||||
|
}
|
||||||
|
serializer
|
||||||
|
.serialize_into(&mut *w, "method")
|
||||||
|
.map_err(|err| revision::Error::Serialize(err.to_string()))?;
|
||||||
|
self.method.serialize_revisioned(w)?;
|
||||||
|
|
||||||
|
if let Some(x) = self.params.as_ref() {
|
||||||
|
serializer
|
||||||
|
.serialize_into(&mut *w, "params")
|
||||||
|
.map_err(|err| revision::Error::Serialize(err.to_string()))?;
|
||||||
|
x.serialize_revisioned(w)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_revisioned<R: Read>(_: &mut R) -> std::result::Result<Self, revision::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
panic!("deliberately unimplemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RouterState<Sink, Stream, Msg> {
|
||||||
|
var_stash: IndexMap<i64, (String, Value)>,
|
||||||
|
/// Vars currently set by the set method,
|
||||||
|
vars: IndexMap<String, Value>,
|
||||||
|
/// Messages which aught to be replayed on a reconnect.
|
||||||
|
replay: IndexMap<Method, Msg>,
|
||||||
|
/// Pending live queries
|
||||||
|
live_queries: HashMap<Uuid, channel::Sender<CoreNotification>>,
|
||||||
|
|
||||||
|
routes: HashMap<i64, (Method, Sender<Result<DbResponse>>)>,
|
||||||
|
|
||||||
|
last_activity: Instant,
|
||||||
|
|
||||||
|
sink: Sink,
|
||||||
|
stream: Stream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Sink, Stream, Msg> RouterState<Sink, Stream, Msg> {
|
||||||
|
pub fn new(sink: Sink, stream: Stream) -> Self {
|
||||||
|
RouterState {
|
||||||
|
var_stash: IndexMap::new(),
|
||||||
|
vars: IndexMap::new(),
|
||||||
|
replay: IndexMap::new(),
|
||||||
|
live_queries: HashMap::new(),
|
||||||
|
routes: HashMap::new(),
|
||||||
|
last_activity: Instant::now(),
|
||||||
|
sink,
|
||||||
|
stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HandleResult {
|
||||||
|
/// Socket disconnected, should continue to reconnect
|
||||||
|
Disconnected,
|
||||||
|
/// Nothing wrong continue as normal.
|
||||||
|
Ok,
|
||||||
|
}
|
||||||
|
|
||||||
/// The WS scheme used to connect to `ws://` endpoints
|
/// The WS scheme used to connect to `ws://` endpoints
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ws;
|
pub struct Ws;
|
||||||
|
@ -156,7 +296,10 @@ pub(crate) struct Response {
|
||||||
pub(crate) result: ServerResult,
|
pub(crate) result: ServerResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(value: &Value, revisioned: bool) -> Result<Vec<u8>> {
|
fn serialize<V>(value: &V, revisioned: bool) -> Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
V: serde::Serialize + Revisioned,
|
||||||
|
{
|
||||||
if revisioned {
|
if revisioned {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
value.serialize_revisioned(&mut buf).map_err(|error| crate::Error::Db(error.into()))?;
|
value.serialize_revisioned(&mut buf).map_err(|error| crate::Error::Db(error.into()))?;
|
||||||
|
@ -177,3 +320,67 @@ where
|
||||||
bytes.read_to_end(&mut buf).map_err(crate::err::Error::Io)?;
|
bytes.read_to_end(&mut buf).map_err(crate::err::Error::Io)?;
|
||||||
crate::sql::serde::deserialize(&buf).map_err(|error| crate::Error::Db(error.into()))
|
crate::sql::serde::deserialize(&buf).map_err(|error| crate::Error::Db(error.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use revision::Revisioned;
|
||||||
|
use surrealdb_core::sql::Value;
|
||||||
|
|
||||||
|
use super::RouterRequest;
|
||||||
|
|
||||||
|
fn assert_converts<S, D, I>(req: &RouterRequest, s: S, d: D)
|
||||||
|
where
|
||||||
|
S: FnOnce(&RouterRequest) -> I,
|
||||||
|
D: FnOnce(I) -> Value,
|
||||||
|
{
|
||||||
|
let ser = s(req);
|
||||||
|
let val = d(ser);
|
||||||
|
let Value::Object(obj) = val else {
|
||||||
|
panic!("not an object");
|
||||||
|
};
|
||||||
|
assert_eq!(obj.get("id").cloned(), req.id);
|
||||||
|
assert_eq!(obj.get("method").unwrap().clone(), req.method);
|
||||||
|
assert_eq!(obj.get("params").cloned(), req.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn router_request_value_conversion() {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: Some(Value::from(1234i64)),
|
||||||
|
method: Value::from("request"),
|
||||||
|
params: Some(vec![Value::from(1234i64), Value::from("request")].into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("test convert bincode");
|
||||||
|
|
||||||
|
assert_converts(
|
||||||
|
&request,
|
||||||
|
|i| bincode::serialize(i).unwrap(),
|
||||||
|
|b| bincode::deserialize(&b).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("test convert json");
|
||||||
|
|
||||||
|
assert_converts(
|
||||||
|
&request,
|
||||||
|
|i| serde_json::to_string(i).unwrap(),
|
||||||
|
|b| serde_json::from_str(&b).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("test convert revisioned");
|
||||||
|
|
||||||
|
assert_converts(
|
||||||
|
&request,
|
||||||
|
|i| {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
i.serialize_revisioned(&mut Cursor::new(&mut buf)).unwrap();
|
||||||
|
buf
|
||||||
|
},
|
||||||
|
|b| Value::deserialize_revisioned(&mut Cursor::new(b)).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::PATH;
|
use super::PATH;
|
||||||
use super::{deserialize, serialize};
|
use super::{deserialize, serialize};
|
||||||
|
use super::{HandleResult, RouterRequest};
|
||||||
use crate::api::conn::Connection;
|
use crate::api::conn::Connection;
|
||||||
use crate::api::conn::DbResponse;
|
use crate::api::conn::DbResponse;
|
||||||
use crate::api::conn::Method;
|
use crate::api::conn::Method;
|
||||||
|
@ -23,16 +24,12 @@ use crate::engine::IntervalStream;
|
||||||
use crate::opt::WaitFor;
|
use crate::opt::WaitFor;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use futures::stream::SplitSink;
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
use futures::SinkExt;
|
use futures::SinkExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures_concurrency::stream::Merge as _;
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -55,19 +52,15 @@ use tokio_tungstenite::MaybeTlsStream;
|
||||||
use tokio_tungstenite::WebSocketStream;
|
use tokio_tungstenite::WebSocketStream;
|
||||||
use trice::Instant;
|
use trice::Instant;
|
||||||
|
|
||||||
type WsResult<T> = std::result::Result<T, WsError>;
|
|
||||||
|
|
||||||
pub(crate) const MAX_MESSAGE_SIZE: usize = 64 << 20; // 64 MiB
|
pub(crate) const MAX_MESSAGE_SIZE: usize = 64 << 20; // 64 MiB
|
||||||
pub(crate) const MAX_FRAME_SIZE: usize = 16 << 20; // 16 MiB
|
pub(crate) const MAX_FRAME_SIZE: usize = 16 << 20; // 16 MiB
|
||||||
pub(crate) const WRITE_BUFFER_SIZE: usize = 128000; // tungstenite default
|
pub(crate) const WRITE_BUFFER_SIZE: usize = 128000; // tungstenite default
|
||||||
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = WRITE_BUFFER_SIZE + MAX_MESSAGE_SIZE; // Recommended max according to tungstenite docs
|
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = WRITE_BUFFER_SIZE + MAX_MESSAGE_SIZE; // Recommended max according to tungstenite docs
|
||||||
pub(crate) const NAGLE_ALG: bool = false;
|
pub(crate) const NAGLE_ALG: bool = false;
|
||||||
|
|
||||||
pub(crate) enum Either {
|
type MessageSink = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
|
||||||
Request(Option<Route>),
|
type MessageStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
|
||||||
Response(WsResult<Message>),
|
type RouterState = super::RouterState<MessageSink, MessageStream, Message>;
|
||||||
Ping,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||||
impl From<Tls> for Connector {
|
impl From<Tls> for Connector {
|
||||||
|
@ -144,7 +137,7 @@ impl Connection for Client {
|
||||||
capacity => flume::bounded(capacity),
|
capacity => flume::bounded(capacity),
|
||||||
};
|
};
|
||||||
|
|
||||||
router(address, maybe_connector, capacity, config, socket, route_rx);
|
tokio::spawn(run_router(address, maybe_connector, capacity, config, socket, route_rx));
|
||||||
|
|
||||||
let mut features = HashSet::new();
|
let mut features = HashSet::new();
|
||||||
features.insert(ExtraFeatures::LiveQueries);
|
features.insert(ExtraFeatures::LiveQueries);
|
||||||
|
@ -172,66 +165,20 @@ impl Connection for Client {
|
||||||
request: (self.id, self.method, param),
|
request: (self.id, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
async fn router_handle_route(
|
||||||
pub(crate) fn router(
|
Route {
|
||||||
endpoint: Endpoint,
|
|
||||||
maybe_connector: Option<Connector>,
|
|
||||||
capacity: usize,
|
|
||||||
config: WebSocketConfig,
|
|
||||||
mut socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
|
|
||||||
route_rx: Receiver<Option<Route>>,
|
|
||||||
) {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let ping = {
|
|
||||||
let mut request = BTreeMap::new();
|
|
||||||
request.insert("method".to_owned(), PING_METHOD.into());
|
|
||||||
let value = Value::from(request);
|
|
||||||
let value = serialize(&value, endpoint.supports_revision).unwrap();
|
|
||||||
Message::Binary(value)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut var_stash = IndexMap::new();
|
|
||||||
let mut vars = IndexMap::new();
|
|
||||||
let mut replay = IndexMap::new();
|
|
||||||
|
|
||||||
'router: loop {
|
|
||||||
let (socket_sink, socket_stream) = socket.split();
|
|
||||||
let mut socket_sink = Socket(Some(socket_sink));
|
|
||||||
|
|
||||||
if let Socket(Some(socket_sink)) = &mut socket_sink {
|
|
||||||
let mut routes = match capacity {
|
|
||||||
0 => HashMap::new(),
|
|
||||||
capacity => HashMap::with_capacity(capacity),
|
|
||||||
};
|
|
||||||
let mut live_queries = HashMap::new();
|
|
||||||
|
|
||||||
let mut interval = time::interval(PING_INTERVAL);
|
|
||||||
// don't bombard the server with pings if we miss some ticks
|
|
||||||
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
|
||||||
|
|
||||||
let pinger = IntervalStream::new(interval);
|
|
||||||
|
|
||||||
let streams = (
|
|
||||||
socket_stream.map(Either::Response),
|
|
||||||
route_rx.stream().map(Either::Request),
|
|
||||||
pinger.map(|_| Either::Ping),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut merged = streams.merge();
|
|
||||||
let mut last_activity = Instant::now();
|
|
||||||
|
|
||||||
while let Some(either) = merged.next().await {
|
|
||||||
match either {
|
|
||||||
Either::Request(Some(Route {
|
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
})) => {
|
}: Route,
|
||||||
|
state: &mut RouterState,
|
||||||
|
endpoint: &Endpoint,
|
||||||
|
) -> HandleResult {
|
||||||
let (id, method, param) = request;
|
let (id, method, param) = request;
|
||||||
let params = match param.query {
|
let params = match param.query {
|
||||||
Some((query, bindings)) => {
|
Some((query, bindings)) => {
|
||||||
|
@ -242,33 +189,28 @@ pub(crate) fn router(
|
||||||
match method {
|
match method {
|
||||||
Method::Set => {
|
Method::Set => {
|
||||||
if let [Value::Strand(key), value] = ¶ms[..2] {
|
if let [Value::Strand(key), value] = ¶ms[..2] {
|
||||||
var_stash.insert(id, (key.0.clone(), value.clone()));
|
state.var_stash.insert(id, (key.0.clone(), value.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method::Unset => {
|
Method::Unset => {
|
||||||
if let [Value::Strand(key)] = ¶ms[..1] {
|
if let [Value::Strand(key)] = ¶ms[..1] {
|
||||||
vars.swap_remove(&key.0);
|
state.vars.swap_remove(&key.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method::Live => {
|
Method::Live => {
|
||||||
if let Some(sender) = param.notification_sender {
|
if let Some(sender) = param.notification_sender {
|
||||||
if let [Value::Uuid(id)] = ¶ms[..1] {
|
if let [Value::Uuid(id)] = ¶ms[..1] {
|
||||||
live_queries.insert(*id, sender);
|
state.live_queries.insert(id.0, sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if response
|
if response.clone().into_send_async(Ok(DbResponse::Other(Value::None))).await.is_err() {
|
||||||
.into_send_async(Ok(DbResponse::Other(Value::None)))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
trace!("Receiver dropped");
|
trace!("Receiver dropped");
|
||||||
}
|
}
|
||||||
// There is nothing to send to the server here
|
// There is nothing to send to the server here
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
Method::Kill => {
|
Method::Kill => {
|
||||||
if let [Value::Uuid(id)] = ¶ms[..1] {
|
if let [Value::Uuid(id)] = ¶ms[..1] {
|
||||||
live_queries.remove(id);
|
state.live_queries.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -278,16 +220,14 @@ pub(crate) fn router(
|
||||||
_ => method.as_str(),
|
_ => method.as_str(),
|
||||||
};
|
};
|
||||||
let message = {
|
let message = {
|
||||||
let mut request = BTreeMap::new();
|
let request = RouterRequest {
|
||||||
request.insert("id".to_owned(), Value::from(id));
|
id: Some(Value::from(id)),
|
||||||
request.insert("method".to_owned(), method_str.into());
|
method: method_str.into(),
|
||||||
if !params.is_empty() {
|
params: (!params.is_empty()).then(|| params.into()),
|
||||||
request.insert("params".to_owned(), params.into());
|
};
|
||||||
}
|
|
||||||
let payload = Value::from(request);
|
trace!("Request {:?}", request);
|
||||||
trace!("Request {payload}");
|
let payload = serialize(&request, endpoint.supports_revision).unwrap();
|
||||||
let payload =
|
|
||||||
serialize(&payload, endpoint.supports_revision).unwrap();
|
|
||||||
Message::Binary(payload)
|
Message::Binary(payload)
|
||||||
};
|
};
|
||||||
if let Method::Authenticate
|
if let Method::Authenticate
|
||||||
|
@ -296,23 +236,19 @@ pub(crate) fn router(
|
||||||
| Method::Signup
|
| Method::Signup
|
||||||
| Method::Use = method
|
| Method::Use = method
|
||||||
{
|
{
|
||||||
replay.insert(method, message.clone());
|
state.replay.insert(method, message.clone());
|
||||||
}
|
}
|
||||||
match socket_sink.send(message).await {
|
match state.sink.send(message).await {
|
||||||
Ok(..) => {
|
Ok(_) => {
|
||||||
last_activity = Instant::now();
|
state.last_activity = Instant::now();
|
||||||
match routes.entry(id) {
|
match state.routes.entry(id) {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
// Register query route
|
// Register query route
|
||||||
entry.insert((method, response));
|
entry.insert((method, response));
|
||||||
}
|
}
|
||||||
Entry::Occupied(..) => {
|
Entry::Occupied(..) => {
|
||||||
let error = Error::DuplicateRequestId(id);
|
let error = Error::DuplicateRequestId(id);
|
||||||
if response
|
if response.into_send_async(Err(error.into())).await.is_err() {
|
||||||
.into_send_async(Err(error.into()))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
trace!("Receiver dropped");
|
trace!("Receiver dropped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,15 +259,18 @@ pub(crate) fn router(
|
||||||
if response.into_send_async(Err(error.into())).await.is_err() {
|
if response.into_send_async(Err(error.into())).await.is_err() {
|
||||||
trace!("Receiver dropped");
|
trace!("Receiver dropped");
|
||||||
}
|
}
|
||||||
break;
|
return HandleResult::Disconnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
HandleResult::Ok
|
||||||
Either::Response(result) => {
|
}
|
||||||
last_activity = Instant::now();
|
|
||||||
match result {
|
async fn router_handle_response(
|
||||||
Ok(message) => {
|
response: Message,
|
||||||
match Response::try_from(&message, endpoint.supports_revision) {
|
state: &mut RouterState,
|
||||||
|
endpoint: &Endpoint,
|
||||||
|
) -> HandleResult {
|
||||||
|
match Response::try_from(&response, endpoint.supports_revision) {
|
||||||
Ok(option) => {
|
Ok(option) => {
|
||||||
// We are only interested in responses that are not empty
|
// We are only interested in responses that are not empty
|
||||||
if let Some(response) = option {
|
if let Some(response) = option {
|
||||||
|
@ -341,100 +280,60 @@ pub(crate) fn router(
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
if let Ok(id) = id.coerce_to_i64() {
|
if let Ok(id) = id.coerce_to_i64() {
|
||||||
// We can only route responses with IDs
|
// We can only route responses with IDs
|
||||||
if let Some((method, sender)) =
|
if let Some((method, sender)) = state.routes.remove(&id) {
|
||||||
routes.remove(&id)
|
|
||||||
{
|
|
||||||
if matches!(method, Method::Set) {
|
if matches!(method, Method::Set) {
|
||||||
if let Some((key, value)) =
|
if let Some((key, value)) = state.var_stash.swap_remove(&id) {
|
||||||
var_stash.swap_remove(&id)
|
state.vars.insert(key, value);
|
||||||
{
|
|
||||||
vars.insert(key, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Send the response back to the caller
|
// Send the response back to the caller
|
||||||
let mut response = response.result;
|
let mut response = response.result;
|
||||||
if matches!(method, Method::Insert)
|
if matches!(method, Method::Insert) {
|
||||||
{
|
|
||||||
// For insert, we need to flatten single responses in an array
|
// For insert, we need to flatten single responses in an array
|
||||||
if let Ok(Data::Other(
|
if let Ok(Data::Other(Value::Array(value))) = &mut response {
|
||||||
Value::Array(value),
|
if let [value] = &mut value.0[..] {
|
||||||
)) = &mut response
|
response = Ok(Data::Other(mem::take(value)));
|
||||||
{
|
|
||||||
if let [value] =
|
|
||||||
&mut value.0[..]
|
|
||||||
{
|
|
||||||
response =
|
|
||||||
Ok(Data::Other(
|
|
||||||
mem::take(
|
|
||||||
value,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _res = sender
|
let _res = sender.into_send_async(DbResponse::from(response)).await;
|
||||||
.into_send_async(
|
|
||||||
DbResponse::from(response),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If `id` is not set, this may be a live query notification
|
// If `id` is not set, this may be a live query notification
|
||||||
None => match response.result {
|
None => {
|
||||||
|
match response.result {
|
||||||
Ok(Data::Live(notification)) => {
|
Ok(Data::Live(notification)) => {
|
||||||
let live_query_id = notification.id;
|
let live_query_id = notification.id;
|
||||||
// Check if this live query is registered
|
// Check if this live query is registered
|
||||||
if let Some(sender) =
|
if let Some(sender) = state.live_queries.get(&live_query_id) {
|
||||||
live_queries.get(&live_query_id)
|
|
||||||
{
|
|
||||||
// Send the notification back to the caller or kill live query if the receiver is already dropped
|
// Send the notification back to the caller or kill live query if the receiver is already dropped
|
||||||
if sender
|
if sender.send(notification).await.is_err() {
|
||||||
.send(notification)
|
state.live_queries.remove(&live_query_id);
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
live_queries
|
|
||||||
.remove(&live_query_id);
|
|
||||||
let kill = {
|
let kill = {
|
||||||
let mut request =
|
let request = RouterRequest {
|
||||||
BTreeMap::new();
|
id: None,
|
||||||
request.insert(
|
method: Method::Kill.as_str().into(),
|
||||||
"method".to_owned(),
|
params: Some(
|
||||||
Method::Kill
|
vec![Value::from(live_query_id)].into(),
|
||||||
.as_str()
|
),
|
||||||
.into(),
|
};
|
||||||
);
|
|
||||||
request.insert(
|
|
||||||
"params".to_owned(),
|
|
||||||
vec![Value::from(
|
|
||||||
live_query_id,
|
|
||||||
)]
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
let value =
|
let value =
|
||||||
Value::from(request);
|
serialize(&request, endpoint.supports_revision)
|
||||||
let value = serialize(
|
|
||||||
&value,
|
|
||||||
endpoint
|
|
||||||
.supports_revision,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Message::Binary(value)
|
Message::Binary(value)
|
||||||
};
|
};
|
||||||
if let Err(error) =
|
if let Err(error) = state.sink.send(kill).await {
|
||||||
socket_sink.send(kill).await
|
|
||||||
{
|
|
||||||
trace!("failed to send kill query to the server; {error:?}");
|
trace!("failed to send kill query to the server; {error:?}");
|
||||||
break;
|
return HandleResult::Disconnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(..) => { /* Ignored responses like pings */
|
Ok(..) => { /* Ignored responses like pings */ }
|
||||||
}
|
|
||||||
Err(error) => error!("{error:?}"),
|
Err(error) => error!("{error:?}"),
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,34 +345,170 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try to find out the ID of the response that failed to deserialise
|
// Let's try to find out the ID of the response that failed to deserialise
|
||||||
if let Message::Binary(binary) = message {
|
if let Message::Binary(binary) = response {
|
||||||
if let Ok(Response {
|
if let Ok(Response {
|
||||||
id,
|
id,
|
||||||
}) = deserialize(
|
}) = deserialize(&mut &binary[..], endpoint.supports_revision)
|
||||||
&mut &binary[..],
|
{
|
||||||
endpoint.supports_revision,
|
|
||||||
) {
|
|
||||||
// Return an error if an ID was returned
|
// Return an error if an ID was returned
|
||||||
if let Some(Ok(id)) =
|
if let Some(Ok(id)) = id.map(Value::coerce_to_i64) {
|
||||||
id.map(Value::coerce_to_i64)
|
if let Some((_method, sender)) = state.routes.remove(&id) {
|
||||||
{
|
let _res = sender.into_send_async(Err(error)).await;
|
||||||
if let Some((_method, sender)) =
|
|
||||||
routes.remove(&id)
|
|
||||||
{
|
|
||||||
let _res = sender
|
|
||||||
.into_send_async(Err(error))
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Unfortunately, we don't know which response failed to deserialize
|
// Unfortunately, we don't know which response failed to deserialize
|
||||||
warn!(
|
warn!("Failed to deserialise message; {error:?}");
|
||||||
"Failed to deserialise message; {error:?}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HandleResult::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn router_reconnect(
|
||||||
|
maybe_connector: &Option<Connector>,
|
||||||
|
config: &WebSocketConfig,
|
||||||
|
state: &mut RouterState,
|
||||||
|
endpoint: &Endpoint,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
trace!("Reconnecting...");
|
||||||
|
match connect(endpoint, Some(*config), maybe_connector.clone()).await {
|
||||||
|
Ok(s) => {
|
||||||
|
let (new_sink, new_stream) = s.split();
|
||||||
|
state.sink = new_sink;
|
||||||
|
state.stream = new_stream;
|
||||||
|
for (_, message) in &state.replay {
|
||||||
|
if let Err(error) = state.sink.send(message.clone()).await {
|
||||||
|
trace!("{error}");
|
||||||
|
time::sleep(time::Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key, value) in &state.vars {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: None,
|
||||||
|
method: Method::Set.as_str().into(),
|
||||||
|
params: Some(vec![key.as_str().into(), value.clone()].into()),
|
||||||
|
};
|
||||||
|
trace!("Request {:?}", request);
|
||||||
|
let payload = serialize(&request, endpoint.supports_revision).unwrap();
|
||||||
|
if let Err(error) = state.sink.send(Message::Binary(payload)).await {
|
||||||
|
trace!("{error}");
|
||||||
|
time::sleep(time::Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace!("Reconnected successfully");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
trace!("Failed to reconnect; {error}");
|
||||||
|
time::sleep(time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn run_router(
|
||||||
|
endpoint: Endpoint,
|
||||||
|
maybe_connector: Option<Connector>,
|
||||||
|
_capacity: usize,
|
||||||
|
config: WebSocketConfig,
|
||||||
|
socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
|
||||||
|
route_rx: Receiver<Route>,
|
||||||
|
) {
|
||||||
|
let ping = {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: None,
|
||||||
|
method: PING_METHOD.into(),
|
||||||
|
params: None,
|
||||||
|
};
|
||||||
|
let value = serialize(&request, endpoint.supports_revision).unwrap();
|
||||||
|
Message::Binary(value)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (socket_sink, socket_stream) = socket.split();
|
||||||
|
let mut state = RouterState::new(socket_sink, socket_stream);
|
||||||
|
let mut route_stream = route_rx.into_stream();
|
||||||
|
|
||||||
|
'router: loop {
|
||||||
|
let mut interval = time::interval(PING_INTERVAL);
|
||||||
|
// don't bombard the server with pings if we miss some ticks
|
||||||
|
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||||
|
|
||||||
|
let mut pinger = IntervalStream::new(interval);
|
||||||
|
// Turn into a stream instead of calling recv_async
|
||||||
|
// The stream seems to be able to keep some state which would otherwise need to be
|
||||||
|
// recreated with each next.
|
||||||
|
|
||||||
|
state.last_activity = Instant::now();
|
||||||
|
state.live_queries.clear();
|
||||||
|
state.routes.clear();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
route = route_stream.next() => {
|
||||||
|
// handle incoming route
|
||||||
|
|
||||||
|
let Some(response) = route else {
|
||||||
|
// route returned none, frontend dropped the channel, meaning the router
|
||||||
|
// should quit.
|
||||||
|
match state.sink.send(Message::Close(None)).await {
|
||||||
|
Ok(..) => trace!("Connection closed successfully"),
|
||||||
|
Err(error) => {
|
||||||
|
warn!("Failed to close database connection; {error}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break 'router;
|
||||||
|
};
|
||||||
|
|
||||||
|
match router_handle_route(response, &mut state, &endpoint).await {
|
||||||
|
HandleResult::Ok => {},
|
||||||
|
HandleResult::Disconnected => {
|
||||||
|
router_reconnect(
|
||||||
|
&maybe_connector,
|
||||||
|
&config,
|
||||||
|
&mut state,
|
||||||
|
&endpoint,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
continue 'router;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = state.stream.next() => {
|
||||||
|
// Handle result from database.
|
||||||
|
|
||||||
|
let Some(result) = result else {
|
||||||
|
// stream returned none meaning the connection dropped, try to reconnect.
|
||||||
|
router_reconnect(
|
||||||
|
&maybe_connector,
|
||||||
|
&config,
|
||||||
|
&mut state,
|
||||||
|
&endpoint,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
continue 'router;
|
||||||
|
};
|
||||||
|
|
||||||
|
state.last_activity = Instant::now();
|
||||||
|
match result {
|
||||||
|
Ok(message) => {
|
||||||
|
match router_handle_response(message, &mut state, &endpoint).await {
|
||||||
|
HandleResult::Ok => continue,
|
||||||
|
HandleResult::Disconnected => {
|
||||||
|
router_reconnect(
|
||||||
|
&maybe_connector,
|
||||||
|
&config,
|
||||||
|
&mut state,
|
||||||
|
&endpoint,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
continue 'router;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
match error {
|
match error {
|
||||||
|
@ -484,72 +519,38 @@ pub(crate) fn router(
|
||||||
trace!("{error}");
|
trace!("{error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
router_reconnect(
|
||||||
|
&maybe_connector,
|
||||||
|
&config,
|
||||||
|
&mut state,
|
||||||
|
&endpoint,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
continue 'router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Either::Ping => {
|
_ = pinger.next() => {
|
||||||
// only ping if we haven't talked to the server recently
|
// only ping if we haven't talked to the server recently
|
||||||
if last_activity.elapsed() >= PING_INTERVAL {
|
if state.last_activity.elapsed() >= PING_INTERVAL {
|
||||||
trace!("Pinging the server");
|
trace!("Pinging the server");
|
||||||
if let Err(error) = socket_sink.send(ping.clone()).await {
|
if let Err(error) = state.sink.send(ping.clone()).await {
|
||||||
trace!("failed to ping the server; {error:?}");
|
trace!("failed to ping the server; {error:?}");
|
||||||
break;
|
router_reconnect(
|
||||||
}
|
&maybe_connector,
|
||||||
}
|
&config,
|
||||||
}
|
&mut state,
|
||||||
// Close connection request received
|
&endpoint,
|
||||||
Either::Request(None) => {
|
)
|
||||||
match socket_sink.send(Message::Close(None)).await {
|
.await;
|
||||||
Ok(..) => trace!("Connection closed successfully"),
|
continue 'router;
|
||||||
Err(error) => {
|
|
||||||
warn!("Failed to close database connection; {error}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break 'router;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
'reconnect: loop {
|
|
||||||
trace!("Reconnecting...");
|
|
||||||
match connect(&endpoint, Some(config), maybe_connector.clone()).await {
|
|
||||||
Ok(s) => {
|
|
||||||
socket = s;
|
|
||||||
for (_, message) in &replay {
|
|
||||||
if let Err(error) = socket.send(message.clone()).await {
|
|
||||||
trace!("{error}");
|
|
||||||
time::sleep(time::Duration::from_secs(1)).await;
|
|
||||||
continue 'reconnect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (key, value) in &vars {
|
|
||||||
let mut request = BTreeMap::new();
|
|
||||||
request.insert("method".to_owned(), Method::Set.as_str().into());
|
|
||||||
request.insert(
|
|
||||||
"params".to_owned(),
|
|
||||||
vec![key.as_str().into(), value.clone()].into(),
|
|
||||||
);
|
|
||||||
let payload = Value::from(request);
|
|
||||||
trace!("Request {payload}");
|
|
||||||
if let Err(error) = socket.send(Message::Binary(payload.into())).await {
|
|
||||||
trace!("{error}");
|
|
||||||
time::sleep(time::Duration::from_secs(1)).await;
|
|
||||||
continue 'reconnect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace!("Reconnected successfully");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
trace!("Failed to reconnect; {error}");
|
|
||||||
time::sleep(time::Duration::from_secs(1)).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
@ -588,8 +589,6 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Socket(Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>);
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::serialize;
|
use super::serialize;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::PATH;
|
|
||||||
use super::{deserialize, serialize};
|
use super::{deserialize, serialize};
|
||||||
|
use super::{HandleResult, PATH};
|
||||||
use crate::api::conn::Connection;
|
use crate::api::conn::Connection;
|
||||||
use crate::api::conn::DbResponse;
|
use crate::api::conn::DbResponse;
|
||||||
use crate::api::conn::Method;
|
use crate::api::conn::Method;
|
||||||
|
@ -16,27 +16,26 @@ use crate::api::ExtraFeatures;
|
||||||
use crate::api::OnceLockExt;
|
use crate::api::OnceLockExt;
|
||||||
use crate::api::Result;
|
use crate::api::Result;
|
||||||
use crate::api::Surreal;
|
use crate::api::Surreal;
|
||||||
use crate::engine::remote::ws::Data;
|
use crate::engine::remote::ws::{Data, RouterRequest};
|
||||||
use crate::engine::IntervalStream;
|
use crate::engine::IntervalStream;
|
||||||
use crate::opt::WaitFor;
|
use crate::opt::WaitFor;
|
||||||
use crate::sql::Value;
|
use crate::sql::Value;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
|
use futures::FutureExt;
|
||||||
use futures::SinkExt;
|
use futures::SinkExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures_concurrency::stream::Merge as _;
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use pharos::Channel;
|
use pharos::Channel;
|
||||||
|
use pharos::Events;
|
||||||
use pharos::Observable;
|
use pharos::Observable;
|
||||||
use pharos::ObserveConfig;
|
use pharos::ObserveConfig;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicI64;
|
use std::sync::atomic::AtomicI64;
|
||||||
|
@ -48,16 +47,13 @@ use trice::Instant;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
use wasmtimer::tokio as time;
|
use wasmtimer::tokio as time;
|
||||||
use wasmtimer::tokio::MissedTickBehavior;
|
use wasmtimer::tokio::MissedTickBehavior;
|
||||||
use ws_stream_wasm::WsEvent;
|
|
||||||
use ws_stream_wasm::WsMessage as Message;
|
use ws_stream_wasm::WsMessage as Message;
|
||||||
use ws_stream_wasm::WsMeta;
|
use ws_stream_wasm::WsMeta;
|
||||||
|
use ws_stream_wasm::{WsEvent, WsStream};
|
||||||
|
|
||||||
pub(crate) enum Either {
|
type MessageStream = SplitStream<WsStream>;
|
||||||
Request(Option<Route>),
|
type MessageSink = SplitSink<WsStream, Message>;
|
||||||
Response(Message),
|
type RouterState = super::RouterState<MessageSink, MessageStream, Message>;
|
||||||
Event(WsEvent),
|
|
||||||
Ping,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::api::Connection for Client {}
|
impl crate::api::Connection for Client {}
|
||||||
|
|
||||||
|
@ -83,7 +79,7 @@ impl Connection for Client {
|
||||||
|
|
||||||
let (conn_tx, conn_rx) = flume::bounded(1);
|
let (conn_tx, conn_rx) = flume::bounded(1);
|
||||||
|
|
||||||
router(address, capacity, conn_tx, route_rx);
|
spawn_local(run_router(address, capacity, conn_tx, route_rx));
|
||||||
|
|
||||||
conn_rx.into_recv_async().await??;
|
conn_rx.into_recv_async().await??;
|
||||||
|
|
||||||
|
@ -113,24 +109,277 @@ impl Connection for Client {
|
||||||
request: (self.id, self.method, param),
|
request: (self.id, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router.sender.send_async(Some(route)).await?;
|
router.sender.send_async(route).await?;
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn router(
|
async fn router_handle_request(
|
||||||
endpoint: Endpoint,
|
Route {
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
}: Route,
|
||||||
|
state: &mut RouterState,
|
||||||
|
endpoint: &Endpoint,
|
||||||
|
) -> HandleResult {
|
||||||
|
let (id, method, param) = request;
|
||||||
|
let params = match param.query {
|
||||||
|
Some((query, bindings)) => {
|
||||||
|
vec![query.into(), bindings.into()]
|
||||||
|
}
|
||||||
|
None => param.other,
|
||||||
|
};
|
||||||
|
match method {
|
||||||
|
Method::Set => {
|
||||||
|
if let [Value::Strand(key), value] = ¶ms[..2] {
|
||||||
|
state.var_stash.insert(id, (key.0.clone(), value.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method::Unset => {
|
||||||
|
if let [Value::Strand(key)] = ¶ms[..1] {
|
||||||
|
state.vars.swap_remove(&key.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method::Live => {
|
||||||
|
if let Some(sender) = param.notification_sender {
|
||||||
|
if let [Value::Uuid(id)] = ¶ms[..1] {
|
||||||
|
state.live_queries.insert(id.0, sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if response.into_send_async(Ok(DbResponse::Other(Value::None))).await.is_err() {
|
||||||
|
trace!("Receiver dropped");
|
||||||
|
}
|
||||||
|
// There is nothing to send to the server here
|
||||||
|
return HandleResult::Ok;
|
||||||
|
}
|
||||||
|
Method::Kill => {
|
||||||
|
if let [Value::Uuid(id)] = ¶ms[..1] {
|
||||||
|
state.live_queries.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let method_str = match method {
|
||||||
|
Method::Health => PING_METHOD,
|
||||||
|
_ => method.as_str(),
|
||||||
|
};
|
||||||
|
let message = {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: Some(Value::from(id)),
|
||||||
|
method: method_str.into(),
|
||||||
|
params: (!params.is_empty()).then(|| params.into()),
|
||||||
|
};
|
||||||
|
trace!("Request {:?}", request);
|
||||||
|
let payload = serialize(&request, endpoint.supports_revision).unwrap();
|
||||||
|
Message::Binary(payload)
|
||||||
|
};
|
||||||
|
if let Method::Authenticate
|
||||||
|
| Method::Invalidate
|
||||||
|
| Method::Signin
|
||||||
|
| Method::Signup
|
||||||
|
| Method::Use = method
|
||||||
|
{
|
||||||
|
state.replay.insert(method, message.clone());
|
||||||
|
}
|
||||||
|
match state.sink.send(message).await {
|
||||||
|
Ok(..) => {
|
||||||
|
state.last_activity = Instant::now();
|
||||||
|
match state.routes.entry(id) {
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert((method, response));
|
||||||
|
}
|
||||||
|
Entry::Occupied(..) => {
|
||||||
|
let error = Error::DuplicateRequestId(id);
|
||||||
|
if response.into_send_async(Err(error.into())).await.is_err() {
|
||||||
|
trace!("Receiver dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let error = Error::Ws(error.to_string());
|
||||||
|
if response.into_send_async(Err(error.into())).await.is_err() {
|
||||||
|
trace!("Receiver dropped");
|
||||||
|
}
|
||||||
|
return HandleResult::Disconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HandleResult::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn router_handle_response(
|
||||||
|
response: Message,
|
||||||
|
state: &mut RouterState,
|
||||||
|
endpoint: &Endpoint,
|
||||||
|
) -> HandleResult {
|
||||||
|
match Response::try_from(&response, endpoint.supports_revision) {
|
||||||
|
Ok(option) => {
|
||||||
|
// We are only interested in responses that are not empty
|
||||||
|
if let Some(response) = option {
|
||||||
|
trace!("{response:?}");
|
||||||
|
match response.id {
|
||||||
|
// If `id` is set this is a normal response
|
||||||
|
Some(id) => {
|
||||||
|
if let Ok(id) = id.coerce_to_i64() {
|
||||||
|
// We can only route responses with IDs
|
||||||
|
if let Some((method, sender)) = state.routes.remove(&id) {
|
||||||
|
if matches!(method, Method::Set) {
|
||||||
|
if let Some((key, value)) = state.var_stash.swap_remove(&id) {
|
||||||
|
state.vars.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send the response back to the caller
|
||||||
|
let mut response = response.result;
|
||||||
|
if matches!(method, Method::Insert) {
|
||||||
|
// For insert, we need to flatten single responses in an array
|
||||||
|
if let Ok(Data::Other(Value::Array(value))) = &mut response {
|
||||||
|
if let [value] = &mut value.0[..] {
|
||||||
|
response = Ok(Data::Other(mem::take(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _res = sender.into_send_async(DbResponse::from(response)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If `id` is not set, this may be a live query notification
|
||||||
|
None => match response.result {
|
||||||
|
Ok(Data::Live(notification)) => {
|
||||||
|
let live_query_id = notification.id;
|
||||||
|
// Check if this live query is registered
|
||||||
|
if let Some(sender) = state.live_queries.get(&live_query_id) {
|
||||||
|
// Send the notification back to the caller or kill live query if the receiver is already dropped
|
||||||
|
if sender.send(notification).await.is_err() {
|
||||||
|
state.live_queries.remove(&live_query_id);
|
||||||
|
let kill = {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: None,
|
||||||
|
method: Method::Kill.as_str().into(),
|
||||||
|
params: Some(vec![Value::from(live_query_id)].into()),
|
||||||
|
};
|
||||||
|
let value = serialize(&request, endpoint.supports_revision)
|
||||||
|
.unwrap();
|
||||||
|
Message::Binary(value)
|
||||||
|
};
|
||||||
|
if let Err(error) = state.sink.send(kill).await {
|
||||||
|
trace!(
|
||||||
|
"failed to send kill query to the server; {error:?}"
|
||||||
|
);
|
||||||
|
return HandleResult::Disconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(..) => { /* Ignored responses like pings */ }
|
||||||
|
Err(error) => error!("{error:?}"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
struct Response {
|
||||||
|
id: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's try to find out the ID of the response that failed to deserialise
|
||||||
|
if let Message::Binary(binary) = response {
|
||||||
|
if let Ok(Response {
|
||||||
|
id,
|
||||||
|
}) = deserialize(&mut &binary[..], endpoint.supports_revision)
|
||||||
|
{
|
||||||
|
// Return an error if an ID was returned
|
||||||
|
if let Some(Ok(id)) = id.map(Value::coerce_to_i64) {
|
||||||
|
if let Some((_method, sender)) = state.routes.remove(&id) {
|
||||||
|
let _res = sender.into_send_async(Err(error)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unfortunately, we don't know which response failed to deserialize
|
||||||
|
warn!("Failed to deserialise message; {error:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HandleResult::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn router_reconnect(
|
||||||
|
state: &mut RouterState,
|
||||||
|
events: &mut Events<WsEvent>,
|
||||||
|
endpoint: &Endpoint,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
conn_tx: Sender<Result<()>>,
|
|
||||||
route_rx: Receiver<Option<Route>>,
|
|
||||||
) {
|
) {
|
||||||
spawn_local(async move {
|
loop {
|
||||||
|
trace!("Reconnecting...");
|
||||||
let connect = match endpoint.supports_revision {
|
let connect = match endpoint.supports_revision {
|
||||||
true => WsMeta::connect(&endpoint.url, vec![super::REVISION_HEADER]).await,
|
true => WsMeta::connect(&endpoint.url, vec![super::REVISION_HEADER]).await,
|
||||||
false => WsMeta::connect(&endpoint.url, None).await,
|
false => WsMeta::connect(&endpoint.url, None).await,
|
||||||
};
|
};
|
||||||
let (mut ws, mut socket) = match connect {
|
match connect {
|
||||||
|
Ok((mut meta, stream)) => {
|
||||||
|
let (new_sink, new_stream) = stream.split();
|
||||||
|
state.sink = new_sink;
|
||||||
|
state.stream = new_stream;
|
||||||
|
*events = {
|
||||||
|
let result = match capacity {
|
||||||
|
0 => meta.observe(ObserveConfig::default()).await,
|
||||||
|
capacity => meta.observe(Channel::Bounded(capacity).into()).await,
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(events) => events,
|
||||||
|
Err(error) => {
|
||||||
|
trace!("{error}");
|
||||||
|
time::sleep(Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (_, message) in &state.replay {
|
||||||
|
if let Err(error) = state.sink.send(message.clone()).await {
|
||||||
|
trace!("{error}");
|
||||||
|
time::sleep(Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key, value) in &state.vars {
|
||||||
|
let request = RouterRequest {
|
||||||
|
id: None,
|
||||||
|
method: Method::Set.as_str().into(),
|
||||||
|
params: Some(vec![key.as_str().into(), value.clone()].into()),
|
||||||
|
};
|
||||||
|
trace!("Request {:?}", request);
|
||||||
|
let serialize = serialize(&request, false).unwrap();
|
||||||
|
if let Err(error) = state.sink.send(Message::Binary(serialize)).await {
|
||||||
|
trace!("{error}");
|
||||||
|
time::sleep(Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace!("Reconnected successfully");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
trace!("Failed to reconnect; {error}");
|
||||||
|
time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn run_router(
|
||||||
|
endpoint: Endpoint,
|
||||||
|
capacity: usize,
|
||||||
|
conn_tx: Sender<Result<()>>,
|
||||||
|
route_rx: Receiver<Route>,
|
||||||
|
) {
|
||||||
|
let connect = match endpoint.supports_revision {
|
||||||
|
true => WsMeta::connect(&endpoint.url, vec![super::REVISION_HEADER]).await,
|
||||||
|
false => WsMeta::connect(&endpoint.url, None).await,
|
||||||
|
};
|
||||||
|
let (mut ws, socket) = match connect {
|
||||||
Ok(pair) => pair,
|
Ok(pair) => pair,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
let _ = conn_tx.into_send_async(Err(error.into())).await;
|
||||||
|
@ -162,248 +411,65 @@ pub(crate) fn router(
|
||||||
Message::Binary(value)
|
Message::Binary(value)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut var_stash = IndexMap::new();
|
let (socket_sink, socket_stream) = socket.split();
|
||||||
let mut vars = IndexMap::new();
|
|
||||||
let mut replay = IndexMap::new();
|
let mut state = RouterState::new(socket_sink, socket_stream);
|
||||||
|
|
||||||
|
let mut route_stream = route_rx.into_stream();
|
||||||
|
|
||||||
'router: loop {
|
'router: loop {
|
||||||
let (mut socket_sink, socket_stream) = socket.split();
|
|
||||||
|
|
||||||
let mut routes = match capacity {
|
|
||||||
0 => HashMap::new(),
|
|
||||||
capacity => HashMap::with_capacity(capacity),
|
|
||||||
};
|
|
||||||
let mut live_queries = HashMap::new();
|
|
||||||
|
|
||||||
let mut interval = time::interval(PING_INTERVAL);
|
let mut interval = time::interval(PING_INTERVAL);
|
||||||
// don't bombard the server with pings if we miss some ticks
|
// don't bombard the server with pings if we miss some ticks
|
||||||
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||||
|
|
||||||
let pinger = IntervalStream::new(interval);
|
let mut pinger = IntervalStream::new(interval);
|
||||||
|
|
||||||
let streams = (
|
state.last_activity = Instant::now();
|
||||||
socket_stream.map(Either::Response),
|
state.live_queries.clear();
|
||||||
route_rx.stream().map(Either::Request),
|
state.routes.clear();
|
||||||
pinger.map(|_| Either::Ping),
|
|
||||||
events.map(Either::Event),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut merged = streams.merge();
|
loop {
|
||||||
let mut last_activity = Instant::now();
|
futures::select! {
|
||||||
|
route = route_stream.next() => {
|
||||||
while let Some(either) = merged.next().await {
|
let Some(route) = route else {
|
||||||
match either {
|
match ws.close().await {
|
||||||
Either::Request(Some(Route {
|
Ok(..) => trace!("Connection closed successfully"),
|
||||||
request,
|
Err(error) => {
|
||||||
response,
|
warn!("Failed to close database connection; {error}")
|
||||||
})) => {
|
|
||||||
let (id, method, param) = request;
|
|
||||||
let params = match param.query {
|
|
||||||
Some((query, bindings)) => {
|
|
||||||
vec![query.into(), bindings.into()]
|
|
||||||
}
|
}
|
||||||
None => param.other,
|
}
|
||||||
|
break 'router;
|
||||||
};
|
};
|
||||||
match method {
|
|
||||||
Method::Set => {
|
match router_handle_request(route, &mut state,&endpoint).await {
|
||||||
if let [Value::Strand(key), value] = ¶ms[..2] {
|
HandleResult::Ok => {},
|
||||||
var_stash.insert(id, (key.0.clone(), value.clone()));
|
HandleResult::Disconnected => {
|
||||||
|
router_reconnect(&mut state, &mut events, &endpoint, capacity).await;
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method::Unset => {
|
}
|
||||||
if let [Value::Strand(key)] = ¶ms[..1] {
|
message = state.stream.next().fuse() => {
|
||||||
vars.swap_remove(&key.0);
|
let Some(message) = message else {
|
||||||
|
// socket disconnected,
|
||||||
|
router_reconnect(&mut state, &mut events, &endpoint, capacity).await;
|
||||||
|
break
|
||||||
|
};
|
||||||
|
|
||||||
|
state.last_activity = Instant::now();
|
||||||
|
match router_handle_response(message, &mut state,&endpoint).await {
|
||||||
|
HandleResult::Ok => {},
|
||||||
|
HandleResult::Disconnected => {
|
||||||
|
router_reconnect(&mut state, &mut events, &endpoint, capacity).await;
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method::Live => {
|
|
||||||
if let Some(sender) = param.notification_sender {
|
|
||||||
if let [Value::Uuid(id)] = ¶ms[..1] {
|
|
||||||
live_queries.insert(*id, sender);
|
|
||||||
}
|
}
|
||||||
}
|
event = events.next().fuse() => {
|
||||||
if response
|
let Some(event) = event else {
|
||||||
.into_send_async(Ok(DbResponse::Other(Value::None)))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
trace!("Receiver dropped");
|
|
||||||
}
|
|
||||||
// There is nothing to send to the server here
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
Method::Kill => {
|
|
||||||
if let [Value::Uuid(id)] = ¶ms[..1] {
|
|
||||||
live_queries.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let method_str = match method {
|
|
||||||
Method::Health => PING_METHOD,
|
|
||||||
_ => method.as_str(),
|
|
||||||
};
|
};
|
||||||
let message = {
|
match event {
|
||||||
let mut request = BTreeMap::new();
|
|
||||||
request.insert("id".to_owned(), Value::from(id));
|
|
||||||
request.insert("method".to_owned(), method_str.into());
|
|
||||||
if !params.is_empty() {
|
|
||||||
request.insert("params".to_owned(), params.into());
|
|
||||||
}
|
|
||||||
let payload = Value::from(request);
|
|
||||||
trace!("Request {payload}");
|
|
||||||
let payload = serialize(&payload, endpoint.supports_revision).unwrap();
|
|
||||||
Message::Binary(payload)
|
|
||||||
};
|
|
||||||
if let Method::Authenticate
|
|
||||||
| Method::Invalidate
|
|
||||||
| Method::Signin
|
|
||||||
| Method::Signup
|
|
||||||
| Method::Use = method
|
|
||||||
{
|
|
||||||
replay.insert(method, message.clone());
|
|
||||||
}
|
|
||||||
match socket_sink.send(message).await {
|
|
||||||
Ok(..) => {
|
|
||||||
last_activity = Instant::now();
|
|
||||||
match routes.entry(id) {
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert((method, response));
|
|
||||||
}
|
|
||||||
Entry::Occupied(..) => {
|
|
||||||
let error = Error::DuplicateRequestId(id);
|
|
||||||
if response
|
|
||||||
.into_send_async(Err(error.into()))
|
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
trace!("Receiver dropped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
let error = Error::Ws(error.to_string());
|
|
||||||
if response.into_send_async(Err(error.into())).await.is_err() {
|
|
||||||
trace!("Receiver dropped");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Either::Response(message) => {
|
|
||||||
last_activity = Instant::now();
|
|
||||||
match Response::try_from(&message, endpoint.supports_revision) {
|
|
||||||
Ok(option) => {
|
|
||||||
// We are only interested in responses that are not empty
|
|
||||||
if let Some(response) = option {
|
|
||||||
trace!("{response:?}");
|
|
||||||
match response.id {
|
|
||||||
// If `id` is set this is a normal response
|
|
||||||
Some(id) => {
|
|
||||||
if let Ok(id) = id.coerce_to_i64() {
|
|
||||||
// We can only route responses with IDs
|
|
||||||
if let Some((method, sender)) = routes.remove(&id) {
|
|
||||||
if matches!(method, Method::Set) {
|
|
||||||
if let Some((key, value)) =
|
|
||||||
var_stash.swap_remove(&id)
|
|
||||||
{
|
|
||||||
vars.insert(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Send the response back to the caller
|
|
||||||
let mut response = response.result;
|
|
||||||
if matches!(method, Method::Insert) {
|
|
||||||
// For insert, we need to flatten single responses in an array
|
|
||||||
if let Ok(Data::Other(Value::Array(
|
|
||||||
value,
|
|
||||||
))) = &mut response
|
|
||||||
{
|
|
||||||
if let [value] = &mut value.0[..] {
|
|
||||||
response = Ok(Data::Other(
|
|
||||||
mem::take(value),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _res = sender
|
|
||||||
.into_send_async(DbResponse::from(response))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If `id` is not set, this may be a live query notification
|
|
||||||
None => match response.result {
|
|
||||||
Ok(Data::Live(notification)) => {
|
|
||||||
let live_query_id = notification.id;
|
|
||||||
// Check if this live query is registered
|
|
||||||
if let Some(sender) =
|
|
||||||
live_queries.get(&live_query_id)
|
|
||||||
{
|
|
||||||
// Send the notification back to the caller or kill live query if the receiver is already dropped
|
|
||||||
if sender.send(notification).await.is_err() {
|
|
||||||
live_queries.remove(&live_query_id);
|
|
||||||
let kill = {
|
|
||||||
let mut request = BTreeMap::new();
|
|
||||||
request.insert(
|
|
||||||
"method".to_owned(),
|
|
||||||
Method::Kill.as_str().into(),
|
|
||||||
);
|
|
||||||
request.insert(
|
|
||||||
"params".to_owned(),
|
|
||||||
vec![Value::from(live_query_id)]
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
let value = Value::from(request);
|
|
||||||
let value = serialize(
|
|
||||||
&value,
|
|
||||||
endpoint.supports_revision,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Message::Binary(value)
|
|
||||||
};
|
|
||||||
if let Err(error) =
|
|
||||||
socket_sink.send(kill).await
|
|
||||||
{
|
|
||||||
trace!("failed to send kill query to the server; {error:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(..) => { /* Ignored responses like pings */ }
|
|
||||||
Err(error) => error!("{error:?}"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[revisioned(revision = 1)]
|
|
||||||
struct Response {
|
|
||||||
id: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's try to find out the ID of the response that failed to deserialise
|
|
||||||
if let Message::Binary(binary) = message {
|
|
||||||
if let Ok(Response {
|
|
||||||
id,
|
|
||||||
}) = deserialize(&mut &binary[..], endpoint.supports_revision)
|
|
||||||
{
|
|
||||||
// Return an error if an ID was returned
|
|
||||||
if let Some(Ok(id)) = id.map(Value::coerce_to_i64) {
|
|
||||||
if let Some((_method, sender)) = routes.remove(&id) {
|
|
||||||
let _res = sender.into_send_async(Err(error)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unfortunately, we don't know which response failed to deserialize
|
|
||||||
warn!("Failed to deserialise message; {error:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Either::Event(event) => match event {
|
|
||||||
WsEvent::Error => {
|
WsEvent::Error => {
|
||||||
trace!("connection errored");
|
trace!("connection errored");
|
||||||
break;
|
break;
|
||||||
|
@ -413,89 +479,25 @@ pub(crate) fn router(
|
||||||
}
|
}
|
||||||
WsEvent::Closed(..) => {
|
WsEvent::Closed(..) => {
|
||||||
trace!("connection closed");
|
trace!("connection closed");
|
||||||
|
router_reconnect(&mut state, &mut events, &endpoint, capacity).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
}
|
||||||
Either::Ping => {
|
}
|
||||||
// only ping if we haven't talked to the server recently
|
_ = pinger.next().fuse() => {
|
||||||
if last_activity.elapsed() >= PING_INTERVAL {
|
if state.last_activity.elapsed() >= PING_INTERVAL {
|
||||||
trace!("Pinging the server");
|
trace!("Pinging the server");
|
||||||
if let Err(error) = socket_sink.send(ping.clone()).await {
|
if let Err(error) = state.sink.send(ping.clone()).await {
|
||||||
trace!("failed to ping the server; {error:?}");
|
trace!("failed to ping the server; {error:?}");
|
||||||
|
router_reconnect(&mut state, &mut events, &endpoint, capacity).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Close connection request received
|
|
||||||
Either::Request(None) => {
|
|
||||||
match ws.close().await {
|
|
||||||
Ok(..) => trace!("Connection closed successfully"),
|
|
||||||
Err(error) => {
|
|
||||||
warn!("Failed to close database connection; {error}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break 'router;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
'reconnect: loop {
|
|
||||||
trace!("Reconnecting...");
|
|
||||||
let connect = match endpoint.supports_revision {
|
|
||||||
true => WsMeta::connect(&endpoint.url, vec![super::REVISION_HEADER]).await,
|
|
||||||
false => WsMeta::connect(&endpoint.url, None).await,
|
|
||||||
};
|
|
||||||
match connect {
|
|
||||||
Ok((mut meta, stream)) => {
|
|
||||||
socket = stream;
|
|
||||||
events = {
|
|
||||||
let result = match capacity {
|
|
||||||
0 => meta.observe(ObserveConfig::default()).await,
|
|
||||||
capacity => meta.observe(Channel::Bounded(capacity).into()).await,
|
|
||||||
};
|
|
||||||
match result {
|
|
||||||
Ok(events) => events,
|
|
||||||
Err(error) => {
|
|
||||||
trace!("{error}");
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
continue 'reconnect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (_, message) in &replay {
|
|
||||||
if let Err(error) = socket.send(message.clone()).await {
|
|
||||||
trace!("{error}");
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
continue 'reconnect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (key, value) in &vars {
|
|
||||||
let mut request = BTreeMap::new();
|
|
||||||
request.insert("method".to_owned(), Method::Set.as_str().into());
|
|
||||||
request.insert(
|
|
||||||
"params".to_owned(),
|
|
||||||
vec![key.as_str().into(), value.clone()].into(),
|
|
||||||
);
|
|
||||||
let payload = Value::from(request);
|
|
||||||
trace!("Request {payload}");
|
|
||||||
if let Err(error) = socket.send(Message::Binary(payload.into())).await {
|
|
||||||
trace!("{error}");
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
continue 'reconnect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace!("Reconnected successfully");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
trace!("Failed to reconnect; {error}");
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::StreamExt;
|
||||||
use futures_concurrency::stream::Merge;
|
|
||||||
use reblessive::TreeStack;
|
use reblessive::TreeStack;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -93,26 +92,29 @@ fn init(opt: &EngineOptions, dbs: Arc<Datastore>) -> (FutureTask, oneshot::Sende
|
||||||
let ret_status = completed_status.clone();
|
let ret_status = completed_status.clone();
|
||||||
|
|
||||||
// We create a channel that can be streamed that will indicate termination
|
// We create a channel that can be streamed that will indicate termination
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, mut rx) = oneshot::channel();
|
||||||
|
|
||||||
let _fut = spawn_future(async move {
|
let _fut = spawn_future(async move {
|
||||||
let _lifecycle = crate::dbs::LoggingLifecycle::new("heartbeat task".to_string());
|
let _lifecycle = crate::dbs::LoggingLifecycle::new("heartbeat task".to_string());
|
||||||
let ticker = interval_ticker(tick_interval).await;
|
let mut ticker = interval_ticker(tick_interval).await;
|
||||||
let streams = (
|
|
||||||
ticker.map(|i| {
|
|
||||||
trace!("Node agent tick: {:?}", i);
|
|
||||||
Some(i)
|
|
||||||
}),
|
|
||||||
rx.into_stream().map(|_| None),
|
|
||||||
);
|
|
||||||
let mut streams = streams.merge();
|
|
||||||
|
|
||||||
while let Some(Some(_)) = streams.next().await {
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
v = ticker.next() => {
|
||||||
|
// ticker will never return None;
|
||||||
|
let i = v.unwrap();
|
||||||
|
trace!("Node agent tick: {:?}", i);
|
||||||
if let Err(e) = dbs.tick().await {
|
if let Err(e) = dbs.tick().await {
|
||||||
error!("Error running node agent tick: {}", e);
|
error!("Error running node agent tick: {}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ = &mut rx => {
|
||||||
|
// termination requested
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
completed_status.store(true, Ordering::Relaxed);
|
completed_status.store(true, Ordering::Relaxed);
|
||||||
|
@ -136,7 +138,7 @@ fn live_query_change_feed(
|
||||||
let ret_status = completed_status.clone();
|
let ret_status = completed_status.clone();
|
||||||
|
|
||||||
// We create a channel that can be streamed that will indicate termination
|
// We create a channel that can be streamed that will indicate termination
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, mut rx) = oneshot::channel();
|
||||||
|
|
||||||
let _fut = spawn_future(async move {
|
let _fut = spawn_future(async move {
|
||||||
let mut stack = TreeStack::new();
|
let mut stack = TreeStack::new();
|
||||||
|
@ -148,18 +150,15 @@ fn live_query_change_feed(
|
||||||
completed_status.store(true, Ordering::Relaxed);
|
completed_status.store(true, Ordering::Relaxed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ticker = interval_ticker(tick_interval).await;
|
let mut ticker = interval_ticker(tick_interval).await;
|
||||||
let streams = (
|
|
||||||
ticker.map(|i| {
|
|
||||||
trace!("Live query agent tick: {:?}", i);
|
|
||||||
Some(i)
|
|
||||||
}),
|
|
||||||
rx.into_stream().map(|_| None),
|
|
||||||
);
|
|
||||||
let mut streams = streams.merge();
|
|
||||||
|
|
||||||
let opt = Options::default();
|
let opt = Options::default();
|
||||||
while let Some(Some(_)) = streams.next().await {
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
v = ticker.next() => {
|
||||||
|
// ticker will never return None;
|
||||||
|
let i = v.unwrap();
|
||||||
|
trace!("Live query agent tick: {:?}", i);
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
stack.enter(|stk| dbs.process_lq_notifications(stk, &opt)).finish().await
|
stack.enter(|stk| dbs.process_lq_notifications(stk, &opt)).finish().await
|
||||||
{
|
{
|
||||||
|
@ -167,6 +166,13 @@ fn live_query_change_feed(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ = &mut rx => {
|
||||||
|
// termination requested,
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
completed_status.store(true, Ordering::Relaxed);
|
completed_status.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,13 +96,7 @@ impl Connection for Client {
|
||||||
request: (0, self.method, param),
|
request: (0, self.method, param),
|
||||||
response: sender,
|
response: sender,
|
||||||
};
|
};
|
||||||
router
|
router.sender.send_async(route).await.as_ref().map_err(ToString::to_string).unwrap();
|
||||||
.sender
|
|
||||||
.send_async(Some(route))
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.map_err(ToString::to_string)
|
|
||||||
.unwrap();
|
|
||||||
Ok(receiver)
|
Ok(receiver)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,14 @@ use crate::sql::Value;
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
pub(super) fn mock(route_rx: Receiver<Option<Route>>) {
|
pub(super) fn mock(route_rx: Receiver<Route>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stream = route_rx.into_stream();
|
let mut stream = route_rx.into_stream();
|
||||||
|
|
||||||
while let Some(Some(Route {
|
while let Some(Route {
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
})) = stream.next().await
|
}) = stream.next().await
|
||||||
{
|
{
|
||||||
let (_, method, param) = request;
|
let (_, method, param) = request;
|
||||||
let mut params = param.other;
|
let mut params = param.other;
|
||||||
|
|
|
@ -5953,6 +5953,7 @@ pub async fn function_http_disabled() {
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.expect_errors(&[
|
.expect_errors(&[
|
||||||
"Remote HTTP request functions are not enabled",
|
"Remote HTTP request functions are not enabled",
|
||||||
"Remote HTTP request functions are not enabled",
|
"Remote HTTP request functions are not enabled",
|
||||||
|
@ -5960,7 +5961,8 @@ pub async fn function_http_disabled() {
|
||||||
"Remote HTTP request functions are not enabled",
|
"Remote HTTP request functions are not enabled",
|
||||||
"Remote HTTP request functions are not enabled",
|
"Remote HTTP request functions are not enabled",
|
||||||
"Remote HTTP request functions are not enabled",
|
"Remote HTTP request functions are not enabled",
|
||||||
]);
|
])
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for custom defined functions
|
// Tests for custom defined functions
|
||||||
|
|
|
@ -25,5 +25,4 @@ in craneLib.buildPackage (buildSpec // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
inherit (util) version SURREAL_BUILD_METADATA;
|
inherit (util) version SURREAL_BUILD_METADATA;
|
||||||
|
|
||||||
RUSTFLAGS = "--cfg surrealdb_unstable";
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -196,7 +196,7 @@ pub async fn init(
|
||||||
net::init(ct.clone()).await?;
|
net::init(ct.clone()).await?;
|
||||||
// Shutdown and stop closed tasks
|
// Shutdown and stop closed tasks
|
||||||
task_chans.into_iter().for_each(|chan| {
|
task_chans.into_iter().for_each(|chan| {
|
||||||
if let Err(_empty_tuple) = chan.send(()) {
|
if chan.send(()).is_err() {
|
||||||
error!("Failed to send shutdown signal to task");
|
error!("Failed to send shutdown signal to task");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue