mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(cli): leave the runtime running on a stop so stragglers cannot panic
A stop shut the tokio runtime down after joining the workers it owns. Threads it never joins, such as a size lookup or a profile backup that a request started during the stop, then panicked on their next timer with "A Tokio 1.x context was found, but it is being shutdown". The process exits right after a stop, so the runtime is now left for the exit to end. A mount that is retried still shuts its runtime down with the 2 s limit. The acceptance journal check now counts panics, and no longer counts the ENOENT reply errors a stop causes by aborting its FUSE connections. docs/BUGS.md B276.
This commit is contained in:
1 parent
9460c93b50
commit
176112a9cb
4 files changed
+150
-9
No files matched your search
@@ -2467,14 +2467,30 @@ fn mount_once(mountpoint: Option<PathBuf>) -> Result<pdfs_fuse::MountOutcome> {
|
||||
"could not save the refreshed session tokens; the next start may need `pdfs login`"
|
||||
);
|
||||
}
|
||||
// Dropping the runtime waits for every blocking task, however long; a call
|
||||
// still stuck on Drive would hold the stop until systemd kills it (B165).
|
||||
rt.shutdown_timeout(std::time::Duration::from_secs(2));
|
||||
release_runtime(rt, matches!(result, Ok(pdfs_fuse::MountOutcome::Shutdown)));
|
||||
|
||||
let outcome = result.context("mount failed")?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// Let go of a mount's runtime once the mount has ended.
|
||||
///
|
||||
/// On a stop the process exits right after, so the runtime is left running
|
||||
/// for it to end with. Shutting it down first broke every thread the mount
|
||||
/// does not join, such as a size lookup or a profile backup a request started
|
||||
/// during the stop: its next call to Drive panicked with "A Tokio 1.x context
|
||||
/// was found, but it is being shutdown" (`docs/BUGS.md` B276). A mount that
|
||||
/// is about to be tried again does shut it down, because a new runtime
|
||||
/// replaces it. Dropping it would wait for every blocking task, however long;
|
||||
/// a call still stuck on Drive would hold the retry (B165).
|
||||
fn release_runtime(rt: tokio::runtime::Runtime, exiting: bool) {
|
||||
if exiting {
|
||||
std::mem::forget(rt);
|
||||
} else {
|
||||
rt.shutdown_timeout(std::time::Duration::from_secs(2));
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_pin(path: PathBuf) -> Result<()> {
|
||||
match control_request(CtlRequest::Pin {
|
||||
path: path_arg(&path)?,
|
||||
@@ -3944,6 +3960,47 @@ fn control_request(req: CtlRequest) -> Result<CtlResponse> {
|
||||
.with_context(|| format!("talk to mount daemon at {}", socket.display()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod release_runtime_tests {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
/// A thread the mount never joined still reaches Drive after a stop. It
|
||||
/// used to panic on its first timer once the runtime was shut down.
|
||||
#[test]
|
||||
fn a_thread_left_running_at_a_stop_can_still_use_the_runtime() {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let handle = rt.handle().clone();
|
||||
let (released_tx, released_rx) = std::sync::mpsc::channel();
|
||||
let straggler = std::thread::spawn(move || {
|
||||
released_rx.recv().unwrap();
|
||||
handle.block_on(async {
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
7
|
||||
})
|
||||
});
|
||||
release_runtime(rt, true);
|
||||
released_tx.send(()).unwrap();
|
||||
assert_eq!(straggler.join().expect("the thread must not panic"), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mount_tried_again_shuts_its_runtime_down() {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
|
||||
rt.spawn(async move {
|
||||
let _done = done_tx;
|
||||
std::future::pending::<()>().await;
|
||||
});
|
||||
release_runtime(rt, false);
|
||||
// The task is dropped with the runtime, and its sender with it.
|
||||
assert_eq!(
|
||||
done_rx.recv_timeout(Duration::from_secs(5)),
|
||||
Err(std::sync::mpsc::RecvTimeoutError::Disconnected)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pause_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in new issue
Block a user