Make cursor broadcast configurable

This commit is contained in:
Andras Schmelczer 2025-06-07 11:31:14 +01:00
parent e37399dc29
commit b60cb0104b
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
6 changed files with 107 additions and 45 deletions

66
backend/Cargo.lock generated
View file

@ -51,7 +51,7 @@ dependencies = [
"bytes", "bytes",
"cfg-if", "cfg-if",
"http", "http",
"indexmap", "indexmap 2.7.0",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@ -71,7 +71,7 @@ dependencies = [
"aide", "aide",
"axum", "axum",
"axum_typed_multipart", "axum_typed_multipart",
"indexmap", "indexmap 2.7.0",
"schemars", "schemars",
] ]
@ -662,6 +662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde",
] ]
[[package]] [[package]]
@ -966,6 +967,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.2"
@ -983,7 +990,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.15.2",
] ]
[[package]] [[package]]
@ -1298,6 +1305,17 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.0" version = "2.7.0"
@ -1305,7 +1323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.15.2",
"serde", "serde",
] ]
@ -2068,7 +2086,7 @@ dependencies = [
"bytes", "bytes",
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"indexmap", "indexmap 2.7.0",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@ -2177,13 +2195,43 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.7.0",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.34+deprecated" version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.7.0",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@ -2329,9 +2377,9 @@ dependencies = [
"futures-intrusive", "futures-intrusive",
"futures-io", "futures-io",
"futures-util", "futures-util",
"hashbrown", "hashbrown 0.15.2",
"hashlink", "hashlink",
"indexmap", "indexmap 2.7.0",
"log", "log",
"memchr", "memchr",
"once_cell", "once_cell",
@ -2581,6 +2629,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_with",
"serde_yaml", "serde_yaml",
"sqlx", "sqlx",
"sync_lib", "sync_lib",
@ -2722,6 +2771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa",
"num-conv", "num-conv",
"powerfmt", "powerfmt",
"serde", "serde",

View file

@ -1,29 +1,27 @@
database: database:
databases_directory_path: databases databases_directory_path: databases
max_connections_per_vault: 12 max_connections_per_vault: 12
cursor_timeout_seconds: 60
cursor_broadcast_interval_seconds: 1
server: server:
host: 0.0.0.0 host: 0.0.0.0
port: 3000 port: 3000
max_body_size_mb: 512 max_body_size_mb: 512
max_clients_per_vault: 256 max_clients_per_vault: 256
response_timeout_seconds: 60 response_timeout_seconds: 60
users: users:
user_configs: user_configs:
- name: admin - name: admin
token: test-token-change-me token: test-token-change-me
vault_access: vault_access:
type: allow_access_to_all type: allow_access_to_all
- name: other-admin
- name: other-admin token: test-token-change-me2
token: test-token-change-me2 vault_access:
vault_access: type: allow_access_to_all
type: allow_access_to_all - name: test
token: other-test-token
- name: test vault_access:
token: other-test-token type: allow_list
vault_access: allowed:
type: allow_list - default
allowed:
- default

View file

@ -38,6 +38,7 @@ serde_json = "1.0.140"
clap-verbosity-flag = "3.0.3" clap-verbosity-flag = "3.0.3"
bimap = "0.6.3" bimap = "0.6.3"
ts-rs = { version = "10.1", features = ["uuid-impl", "chrono-impl"] } ts-rs = { version = "10.1", features = ["uuid-impl", "chrono-impl"] }
serde_with = "3.12.0"
[lints] [lints]
workspace = true workspace = true

View file

@ -1,8 +1,6 @@
use core::time::Duration; use core::time::Duration;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use chrono::TimeDelta;
use sqlx::types::chrono::Utc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::{ use super::{
@ -10,15 +8,13 @@ use super::{
websocket::{ websocket::{
broadcasts::Broadcasts, broadcasts::Broadcasts,
models::{ models::{
ClientCursors, CursorPositionFromServer, WebSocketServerMessage, ClientCursors, CursorPositionFromServer, CursorSpan, WebSocketServerMessage,
WebSocketServerMessageWithOrigin, WebSocketServerMessageWithOrigin,
}, },
}, },
}; };
use crate::config::database_config::DatabaseConfig; use crate::config::database_config::DatabaseConfig;
const BACKGROUND_TASK_INTERVAL: Duration = Duration::from_secs(1);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Cursors { pub struct Cursors {
config: DatabaseConfig, config: DatabaseConfig,
@ -39,7 +35,7 @@ impl Cursors {
&self, &self,
vault_id: VaultId, vault_id: VaultId,
device_id: &DeviceId, device_id: &DeviceId,
document_to_cursors: HashMap<String, Vec<usize>>, document_to_cursors: HashMap<String, Vec<CursorSpan>>,
) { ) {
let mut vault_to_cursors = self.vault_to_cursors.lock().await; let mut vault_to_cursors = self.vault_to_cursors.lock().await;
@ -76,7 +72,7 @@ impl Cursors {
loop { loop {
self.remove_expired_cursors().await; self.remove_expired_cursors().await;
self.broadcast_cursors().await; self.broadcast_cursors().await;
tokio::time::sleep(BACKGROUND_TASK_INTERVAL).await; tokio::time::sleep(self.config.cursor_broadcast_interval).await;
} }
} }
@ -109,16 +105,16 @@ impl Cursors {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct ClientCursorsWithTimeToLive { struct ClientCursorsWithTimeToLive {
client_cursors: ClientCursors, client_cursors: ClientCursors,
last_updated: chrono::DateTime<Utc>, last_updated: std::time::Instant,
} }
impl ClientCursorsWithTimeToLive { impl ClientCursorsWithTimeToLive {
fn new(client_cursors: ClientCursors) -> Self { fn new(client_cursors: ClientCursors) -> Self {
Self { Self {
client_cursors, client_cursors,
last_updated: Utc::now(), last_updated: std::time::Instant::now(),
} }
} }
pub fn is_expired(&self, ttl: TimeDelta) -> bool { Utc::now() - self.last_updated > ttl } pub fn is_expired(&self, ttl: Duration) -> bool { self.last_updated.elapsed() > ttl }
} }

View file

@ -1,13 +1,15 @@
use std::path::PathBuf; use std::{path::PathBuf, time::Duration};
use chrono::TimeDelta;
use log::debug; use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use crate::consts::{ use crate::consts::{
DEFAULT_CURSOR_TIMEOUT, DEFAULT_DATABASES_DIRECTORY_PATH, DEFAULT_MAX_CONNECTIONS_PER_VAULT, DEFAULT_CURSOR_BROADCAST_INTERVAL, DEFAULT_CURSOR_TIMEOUT, DEFAULT_DATABASES_DIRECTORY_PATH,
DEFAULT_MAX_CONNECTIONS_PER_VAULT,
}; };
#[serde_with::serde_as]
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DatabaseConfig { pub struct DatabaseConfig {
#[serde(default = "default_databases_directory_path")] #[serde(default = "default_databases_directory_path")]
@ -16,8 +18,16 @@ pub struct DatabaseConfig {
#[serde(default = "default_max_connections_per_vault")] #[serde(default = "default_max_connections_per_vault")]
pub max_connections_per_vault: u32, pub max_connections_per_vault: u32,
#[serde(default = "default_cursor_timeout")] #[serde(default = "default_cursor_timeout", rename = "cursor_timeout_seconds")]
pub cursor_timeout: TimeDelta, #[serde_as(as = "serde_with::DurationSeconds<u64>")]
pub cursor_timeout: Duration,
#[serde(
default = "default_cursor_broadcast_interval",
rename = "cursor_broadcast_interval_seconds"
)]
#[serde_as(as = "serde_with::DurationSeconds<u64>")]
pub cursor_broadcast_interval: Duration,
} }
fn default_databases_directory_path() -> PathBuf { fn default_databases_directory_path() -> PathBuf {
@ -30,17 +40,23 @@ fn default_max_connections_per_vault() -> u32 {
DEFAULT_MAX_CONNECTIONS_PER_VAULT DEFAULT_MAX_CONNECTIONS_PER_VAULT
} }
fn default_cursor_timeout() -> TimeDelta { fn default_cursor_timeout() -> Duration {
debug!("Using default cursor timeout: {DEFAULT_CURSOR_TIMEOUT}"); debug!("Using default cursor timeout: {DEFAULT_CURSOR_TIMEOUT:?}");
DEFAULT_CURSOR_TIMEOUT DEFAULT_CURSOR_TIMEOUT
} }
fn default_cursor_broadcast_interval() -> Duration {
debug!("Using default cursor broadcast interval: {DEFAULT_CURSOR_BROADCAST_INTERVAL:?}");
DEFAULT_CURSOR_BROADCAST_INTERVAL
}
impl Default for DatabaseConfig { impl Default for DatabaseConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
databases_directory_path: default_databases_directory_path(), databases_directory_path: default_databases_directory_path(),
max_connections_per_vault: default_max_connections_per_vault(), max_connections_per_vault: default_max_connections_per_vault(),
cursor_timeout: default_cursor_timeout(), cursor_timeout: default_cursor_timeout(),
cursor_broadcast_interval: default_cursor_broadcast_interval(),
} }
} }
} }

View file

@ -1,10 +1,11 @@
use chrono::TimeDelta; use std::time::Duration;
pub const DEFAULT_CONFIG_PATH: &str = "config.yml"; pub const DEFAULT_CONFIG_PATH: &str = "config.yml";
pub const DEFAULT_DATABASES_DIRECTORY_PATH: &str = "databases"; pub const DEFAULT_DATABASES_DIRECTORY_PATH: &str = "databases";
pub const DEFAULT_MAX_CONNECTIONS_PER_VAULT: u32 = 12; pub const DEFAULT_MAX_CONNECTIONS_PER_VAULT: u32 = 12;
pub const DEFAULT_CURSOR_TIMEOUT: TimeDelta = TimeDelta::seconds(60); pub const DEFAULT_CURSOR_TIMEOUT: Duration = Duration::from_secs(60);
pub const DEFAULT_CURSOR_BROADCAST_INTERVAL: Duration = Duration::from_secs(1);
pub const DEFAULT_HOST: &str = "127.0.0.1"; pub const DEFAULT_HOST: &str = "127.0.0.1";
pub const DEFAULT_PORT: u16 = 3000; pub const DEFAULT_PORT: u16 = 3000;