mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 17:07:00 +01:00
Media answers Range requests with 206 and Content-Range, so videos seek and Safari plays them. Sealed files too: the vault's opener now decrypts by record position (STREAM BE32 through StreamPrimitive, the same construction and the same files), so a range starts at the record that holds its first byte. Compression is limited to text, because a gzipped body no longer matches the byte ranges a player asks for. GET /api/health answers without a session once the database does, and the compose healthcheck asks it instead of only opening the port. Requests are logged at info with share tokens and keys left out of the path and no query strings. A request that is not answered in 30 seconds gets a 503, except uploads, imports, the export, re-sealing and backups. The backup, mail, sweep and SFTP loops run under a supervisor that logs a panic and starts the loop again. The backup loop starts without a Proton session and picks up a later proton-login without a restart. A lock file keeps backup-now and the server from mirroring at once, and an unreadable session key file turns backups off instead of failing boot. Numbers in the configuration are checked against bounds at startup, so a session length or an upload limit can no longer overflow later, and a switch that is neither on nor off is an error rather than off. Config and MailConfig print with the password, secret and SMTP URL redacted. A session epoch is signed into each cookie (migration 0014). :logout! advances it, which ends every session, and a changed WORKSPACE_PASSWORD advances it on the next start. Cookies issued before this keep working in the first epoch.
64 lines
2.2 KiB
Rust
64 lines
2.2 KiB
Rust
//! Background loops that outlive a panic in one of their passes.
|
|
//!
|
|
//! A task spawned and forgotten dies silently when it panics, and the server
|
|
//! carries on without backups, or mail, with nothing in the log after the
|
|
//! panic itself. Every long-running loop goes through [`supervise`] instead,
|
|
//! which logs the panic and starts the loop again.
|
|
|
|
use std::{future::Future, time::Duration};
|
|
|
|
/// Long enough that a loop panicking at once does not spin.
|
|
const RESTART: Duration = Duration::from_secs(10);
|
|
|
|
/// Run `make()` for as long as the server lives, starting it again after a
|
|
/// panic. A loop that returns has finished on purpose and is not restarted.
|
|
pub fn supervise<F, Fut>(name: &'static str, make: F)
|
|
where
|
|
F: Fn() -> Fut + Send + 'static,
|
|
Fut: Future<Output = ()> + Send + 'static,
|
|
{
|
|
tokio::spawn(async move {
|
|
loop {
|
|
match tokio::spawn(make()).await {
|
|
Ok(()) => return,
|
|
Err(e) if e.is_panic() => {
|
|
let payload = e.into_panic();
|
|
let message = payload
|
|
.downcast_ref::<&str>()
|
|
.map(|s| s.to_string())
|
|
.or_else(|| payload.downcast_ref::<String>().cloned())
|
|
.unwrap_or_else(|| "(no message)".into());
|
|
tracing::error!("{name} panicked: {message}; starting it again in {RESTART:?}");
|
|
tokio::time::sleep(RESTART).await;
|
|
}
|
|
// Cancelled: the runtime is shutting down.
|
|
Err(_) => return,
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::sync::{
|
|
atomic::{AtomicUsize, Ordering},
|
|
Arc,
|
|
};
|
|
|
|
#[tokio::test(start_paused = true)]
|
|
async fn a_loop_that_panics_is_started_again() {
|
|
let runs = Arc::new(AtomicUsize::new(0));
|
|
let counter = Arc::clone(&runs);
|
|
super::supervise("test", move || {
|
|
let counter = Arc::clone(&counter);
|
|
async move {
|
|
if counter.fetch_add(1, Ordering::SeqCst) == 0 {
|
|
panic!("first pass");
|
|
}
|
|
}
|
|
});
|
|
tokio::time::sleep(super::RESTART * 2).await;
|
|
assert_eq!(runs.load(Ordering::SeqCst), 2);
|
|
}
|
|
}
|