Implement build cancellation (#888)

- Add `texlab.cancelBuild` workspace command to cancel all currently active builds.
- Cancelled builds now return the previously unused `CANCELLED` status.
- Fixes #887.
This commit is contained in:
Patrick Förster 2023-05-20 20:56:23 +02:00 committed by GitHub
parent 2b319fc0d4
commit 68bf2b3d96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 18 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Add `texlab.cancelBuild` command to cancel the currently active build ([#887](https://github.com/latex-lsp/texlab/issues/887))
### Fixed
- Fix resolving include commands from the `import` package ([#885](https://github.com/latex-lsp/texlab/issues/885))

5
Cargo.lock generated
View file

@ -323,6 +323,7 @@ dependencies = [
"bstr",
"crossbeam-channel",
"itertools",
"libc",
"log",
"rowan",
"rustc-hash",
@ -872,9 +873,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "linked-hash-map"

View file

@ -12,6 +12,7 @@ base-db = { path = "../base-db" }
bstr = "1.4.0"
crossbeam-channel = "0.5.8"
itertools = "0.10.5"
libc = "0.2.144"
log = "0.4.17"
rowan = "0.15.11"
rustc-hash = "1.1.0"

View file

@ -1,7 +1,7 @@
use std::{
io::{BufReader, Read},
path::{Path, PathBuf},
process::{ExitStatus, Stdio},
process::{Child, Stdio},
thread::{self, JoinHandle},
};
@ -64,7 +64,7 @@ impl BuildCommand {
})
}
pub fn run(self, sender: Sender<String>) -> Result<ExitStatus, BuildError> {
pub fn spawn(self, sender: Sender<String>) -> Result<Child, BuildError> {
log::debug!(
"Spawning compiler {} {:#?} in directory {}",
self.program,
@ -72,19 +72,56 @@ impl BuildCommand {
self.working_dir.display()
);
let mut process = std::process::Command::new(&self.program)
.args(self.args)
let mut process = self.spawn_internal()?;
track_output(process.stderr.take().unwrap(), sender.clone());
track_output(process.stdout.take().unwrap(), sender);
Ok(process)
}
#[cfg(windows)]
fn spawn_internal(&self) -> Result<Child, BuildError> {
std::process::Command::new(&self.program)
.args(self.args.clone())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(&self.working_dir)
.spawn()?;
.spawn()
.map_err(Into::into)
}
track_output(process.stderr.take().unwrap(), sender.clone());
track_output(process.stdout.take().unwrap(), sender);
#[cfg(unix)]
fn spawn_internal(&self) -> Result<Child, BuildError> {
use std::os::unix::process::CommandExt;
std::process::Command::new(&self.program)
.args(self.args.clone())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(&self.working_dir)
.process_group(0)
.spawn()
.map_err(Into::into)
}
let status = process.wait();
Ok(status?)
#[cfg(windows)]
pub fn cancel(pid: u32) -> std::io::Result<bool> {
Ok(std::process::Command::new("taskkill")
.arg("/PID")
.arg(pid.to_string())
.arg("/F")
.arg("/T")
.status()?
.success())
}
#[cfg(not(windows))]
pub fn cancel(pid: u32) -> Result<bool> {
unsafe {
libc::killpg(pid as libc::pid_t, libc::SIGTERM);
}
Ok(true)
}
}

View file

@ -67,6 +67,7 @@ pub struct Server {
chktex_diagnostics: FxHashMap<Url, Vec<Diagnostic>>,
watcher: FileWatcher,
pool: ThreadPool,
pending_builds: Arc<Mutex<FxHashSet<u32>>>,
}
impl Server {
@ -86,6 +87,7 @@ impl Server {
chktex_diagnostics: Default::default(),
watcher,
pool: threadpool::Builder::new().build(),
pending_builds: Default::default(),
}
}
@ -672,6 +674,16 @@ impl Server {
self.client
.send_response(lsp_server::Response::new_ok(id, dot))?;
}
"texlab.cancelBuild" => {
let pending_builds = Arc::clone(&self.pending_builds);
self.run_fallible(id, move || {
for pid in pending_builds.lock().drain() {
let _ = BuildCommand::cancel(pid);
}
Ok(())
});
}
_ => {
self.client
.send_error(
@ -728,6 +740,8 @@ impl Server {
let command = BuildCommand::new(&workspace, &uri);
let internal = self.internal_tx.clone();
let progress = self.client_capabilities.has_work_done_progress_support();
let pending_builds = Arc::clone(&self.pending_builds);
self.pool.execute(move || {
let guard = LOCK.lock();
@ -738,14 +752,29 @@ impl Server {
None
};
let status = match command.and_then(|command| command.run(sender)) {
Ok(status) if status.success() => BuildStatus::SUCCESS,
Ok(_) => BuildStatus::ERROR,
Err(why) => {
let status = command
.and_then(|command| {
let mut process = command.spawn(sender)?;
let pid = process.id();
pending_builds.lock().insert(pid);
let result = process.wait();
let status = if pending_builds.lock().remove(&pid) {
if result?.success() {
BuildStatus::SUCCESS
} else {
BuildStatus::ERROR
}
} else {
BuildStatus::CANCELLED
};
Ok(status)
})
.unwrap_or_else(|why| {
log::error!("Failed to compile document \"{uri}\": {why}");
BuildStatus::FAILURE
}
};
});
drop(progress_reporter);
drop(guard);
@ -755,7 +784,7 @@ impl Server {
let _ = client.send_response(lsp_server::Response::new_ok(id, result));
}
if fwd_search_after {
if fwd_search_after && status != BuildStatus::CANCELLED {
let _ = internal.send(InternalMessage::ForwardSearch(uri, params.position));
}
});