mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Window destruction and the close_project command detach the project at once and let its language servers, kernels and debug sessions shut down in the background; opening a project waits for pending shutdowns and exit waits for them with a bound. Closes #1414
106 lines
4.6 KiB
Rust
106 lines
4.6 KiB
Rust
//! Closing a project takes the background processes of its window with it, as IDEA does,
|
|
//! and leaves the other windows' processes alone.
|
|
#![cfg(unix)]
|
|
|
|
use ide_core::NullSink;
|
|
use ide_host::{Host, HostConfig, ops};
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn options(root: &std::path::Path) -> ide_terminal::TerminalOptions {
|
|
ide_terminal::TerminalOptions {
|
|
cwd: root.to_path_buf().into(),
|
|
// A fixed shell: the test must not depend on the login shell of this machine.
|
|
shell: Some("/bin/sh".into()),
|
|
args: vec![],
|
|
env: Default::default(),
|
|
cols: 80,
|
|
rows: 24,
|
|
shell_integration: false,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn closing_a_project_closes_its_terminals_and_keeps_other_windows() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let root = dunce::canonicalize(tmp.path()).unwrap().join("project");
|
|
std::fs::create_dir_all(&root).unwrap();
|
|
let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(2).enable_all().build().unwrap();
|
|
runtime.block_on(async {
|
|
let host = Arc::new(Host::new(HostConfig {
|
|
config_dir: tmp.path().join("config"),
|
|
data_dir: tmp.path().join("data"),
|
|
local_data_dir: tmp.path().join("data"),
|
|
languages: Arc::new(ide_language::LanguageRegistry::load(None).unwrap()),
|
|
syntax: Arc::default(),
|
|
sink_for: Box::new(|_| Arc::new(NullSink)),
|
|
owner: |w| w,
|
|
runtime: tokio::runtime::Handle::current(),
|
|
project_environment: false,
|
|
}));
|
|
host.trust.decide(&root, true, false).unwrap();
|
|
host.open_project("main", &root).unwrap();
|
|
let project_shell =
|
|
ops::term::spawn(&host, "main", options(&root), Arc::new(|_| {})).expect("project terminal");
|
|
let other_shell = ops::term::spawn(&host, "other", options(&root), Arc::new(|_| {})).expect("other terminal");
|
|
assert_eq!(host.terminals.list().len(), 2);
|
|
|
|
host.close_project("main");
|
|
|
|
let left: Vec<_> = host.terminals.list().into_iter().map(|t| t.id).collect();
|
|
assert!(!left.contains(&project_shell.id), "the closing window's shell is gone");
|
|
assert!(left.contains(&other_shell.id), "another window keeps its shell");
|
|
host.terminals.kill_key("other");
|
|
});
|
|
}
|
|
|
|
/// The desktop closes a project on the event-loop thread while runtime workers may be
|
|
/// blocked waiting for that thread (IPC responses, window queries): closing must not
|
|
/// wait for workers that need the runtime, and exit still waits for them once free.
|
|
#[test]
|
|
fn detached_close_returns_while_runtime_workers_wait_for_the_closing_thread() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let root = dunce::canonicalize(tmp.path()).unwrap().join("project");
|
|
std::fs::create_dir_all(&root).unwrap();
|
|
let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).enable_all().build().unwrap();
|
|
let _guard = runtime.enter();
|
|
let host = Arc::new(Host::new(HostConfig {
|
|
config_dir: tmp.path().join("config"),
|
|
data_dir: tmp.path().join("data"),
|
|
local_data_dir: tmp.path().join("data"),
|
|
languages: Arc::new(ide_language::LanguageRegistry::load(None).unwrap()),
|
|
syntax: Arc::default(),
|
|
sink_for: Box::new(|_| Arc::new(NullSink)),
|
|
owner: |w| w,
|
|
runtime: runtime.handle().clone(),
|
|
project_environment: false,
|
|
}));
|
|
host.trust.decide(&root, true, false).unwrap();
|
|
host.open_project("main", &root).unwrap();
|
|
|
|
// Occupy the only runtime worker until "the event loop" answers.
|
|
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
|
let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
|
|
runtime.spawn(async move {
|
|
started_tx.send(()).unwrap();
|
|
let _ = release_rx.recv();
|
|
});
|
|
started_rx.recv().unwrap();
|
|
|
|
let (closed_tx, closed_rx) = std::sync::mpsc::channel();
|
|
let closer = host.clone();
|
|
std::thread::spawn(move || {
|
|
closer.close_project_detached("main");
|
|
let _ = closed_tx.send(());
|
|
});
|
|
let returned = closed_rx.recv_timeout(Duration::from_secs(5));
|
|
release_tx.send(()).unwrap();
|
|
assert!(returned.is_ok(), "closing waited for a runtime worker that waits for the closing thread");
|
|
assert!(host.project("main").is_err(), "the window no longer has the project");
|
|
host.open_project("main", &root).expect("the window opens a project once the close has finished");
|
|
|
|
let started = Instant::now();
|
|
host.shutdown_notebooks_on_exit();
|
|
assert!(started.elapsed() < Duration::from_secs(5), "exit joined the finished close instead of timing out");
|
|
}
|