Skip to content
19 changes: 15 additions & 4 deletions qlty-check/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ impl Executor {

pub fn install(&self) -> Result<Vec<Message>> {
let mut install_messages = vec![];
let installation_results =
Self::install_tools(self.plan.tools(), self.plan.jobs, self.progress.clone());
let installation_results = Self::install_tools(
self.plan.tools(),
self.plan.jobs,
self.progress.clone(),
Some(self.plan.settings.action_timeout),
);

for installation_result in installation_results {
let (name, result) = installation_result;
Expand Down Expand Up @@ -102,6 +106,7 @@ impl Executor {
tools: Vec<(String, Box<dyn Tool>)>,
jobs: usize,
progress: Progress,
timeout: Option<std::time::Duration>,
) -> Vec<(String, Result<()>)> {
let timer = Instant::now();
let pool = rayon::ThreadPoolBuilder::new()
Expand All @@ -118,7 +123,7 @@ impl Executor {
.map(|(name, tool)| {
(
name.clone(),
Self::install_tool(name, tool, progress.clone()),
Self::install_tool(name, tool, progress.clone(), timeout),
)
})
.collect::<Vec<_>>();
Expand Down Expand Up @@ -216,7 +221,13 @@ impl Executor {
Ok(Results::new(messages, invocations, issues, formatted))
}

fn install_tool(name: String, tool: Box<dyn Tool>, progress: Progress) -> Result<()> {
fn install_tool(
name: String,
tool: Box<dyn Tool>,
progress: Progress,
_timeout: Option<std::time::Duration>,
) -> Result<()> {
// Timeout is now passed through ToolBuilder to the tools that need it
let task = progress.task(&name, "Installing...");
tool.pre_setup(&task)?;
tool.setup(&task)?;
Expand Down
43 changes: 37 additions & 6 deletions qlty-check/src/executor/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ impl Driver {
let timer = Instant::now();
let handle = cmd.start()?;
let pids = handle.pids();
let timeout = plan.driver.timeout;
// Use the minimum of action_timeout and driver timeout
let timeout = std::cmp::min(plan.settings.action_timeout.as_secs(), plan.driver.timeout);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculation of the effective timeout should be moved from the executor to the planner

let invocation_label = plan.invocation_label();
let running = Arc::new(AtomicBool::new(true));
let running_clone = Arc::clone(&running);
Expand Down Expand Up @@ -519,12 +520,42 @@ impl Driver {
let timer = Instant::now();
let invocation_label = plan.invocation_label();

let output = cmd.run().with_context(|| {
// Use action_timeout from settings
let timeout = plan.settings.action_timeout;

// Start the command
let handle = cmd.start().with_context(|| {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the timeout handling down into the cmd code? It looks like we already have a CmdWrapper of some sort.

format!(
"Failed to start prepare_script for {}: {}",
invocation_label, &rerun
)
})?;
let pids = handle.pids();
let running = Arc::new(AtomicBool::new(true));
let running_clone = Arc::clone(&running);
let invocation_label_clone = invocation_label.clone();

// Set up timeout thread
thread::spawn(move || {
thread::sleep(timeout);
if running_clone.load(Ordering::SeqCst) {
error!(
"Killing {} prepare_script after {}s",
invocation_label_clone,
timeout.as_secs()
);
Self::terminate_processes(pids);
}
});

// Wait for the process to complete
let output = handle.into_output().with_context(|| {
format!(
"Failed to run prepare_script for {}: {}",
invocation_label, &rerun
)
})?;
running.store(false, Ordering::SeqCst);

info!(
"{}: prepare_script ran {} in {:.3}s (exit {}): stdout: {}",
Expand Down Expand Up @@ -703,7 +734,7 @@ pub mod test {
runtime_version: None,
plugin_name: "test".to_string(),
plugin: PluginDef::default(),
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver: build_driver(vec![], vec![]),
plugin_configs: vec![],
Expand Down Expand Up @@ -765,7 +796,7 @@ pub mod test {
prefix: Some(prefix.to_string()),
..Default::default()
},
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver: build_driver(vec![], vec![]),
plugin_configs: vec![],
Expand All @@ -790,7 +821,7 @@ pub mod test {
let workspace_dir = PathBuf::from("/var/root");
let target_path = PathBuf::from("basic.py");
let driver = build_driver(vec![], vec![]);
let tool = Ruby::new_tool("");
let tool = Ruby::new_tool("", crate::settings::Settings::default().action_timeout);

let plan = InvocationPlan {
target_root: PathBuf::from(workspace_dir.clone()),
Expand Down Expand Up @@ -841,7 +872,7 @@ pub mod test {
let staging_dir = PathBuf::from("/tmp/staging");
let target_path = PathBuf::from("basic.py");
let driver = build_driver(vec![], vec![]);
let tool = Ruby::new_tool("");
let tool = Ruby::new_tool("", crate::settings::Settings::default().action_timeout);

let plan = InvocationPlan {
target_root: PathBuf::from(staging_dir.clone()),
Expand Down
8 changes: 4 additions & 4 deletions qlty-check/src/executor/invocation_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ mod test {
runtime_version: None,
plugin_name: "test".to_string(),
plugin: PluginDef::default(),
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver: build_driver(vec![], vec![]),
plugin_configs: vec![],
Expand Down Expand Up @@ -227,7 +227,7 @@ mod test {
runtime_version: None,
plugin_name: "test".to_string(),
plugin: PluginDef::default(),
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver: build_driver(vec![], vec![]),
plugin_configs: vec![],
Expand Down Expand Up @@ -271,7 +271,7 @@ mod test {
runtime_version: None,
plugin_name: "test".to_string(),
plugin: PluginDef::default(),
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver: build_driver(vec![], vec![]),
plugin_configs: vec![],
Expand Down Expand Up @@ -312,7 +312,7 @@ mod test {
runtime_version: None,
plugin_name: "test".to_string(),
plugin: PluginDef::default(),
tool: Ruby::new_tool(""),
tool: Ruby::new_tool("", crate::settings::Settings::default().action_timeout),
driver_name: "test".to_string(),
driver,
plugin_configs: vec![],
Expand Down
1 change: 1 addition & 0 deletions qlty-check/src/planner/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ impl PluginPlanner {
};

let tool = ToolBuilder::new(&planner.config, plugin_name, &plugin)
.with_timeout(Some(planner.settings.action_timeout))
.build_tool()
.context("Failed to build tool")?;

Expand Down
4 changes: 3 additions & 1 deletion qlty-check/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use qlty_config::config::CheckTrigger;
use qlty_types::analysis::v1::{Issue, Level};
use std::{fmt::Formatter, path::PathBuf};
use std::{fmt::Formatter, path::PathBuf, time::Duration};

#[derive(Debug, Clone)]
pub struct Settings {
Expand All @@ -26,6 +26,7 @@ pub struct Settings {
pub skip_errored_plugins: bool,
pub emit_existing_issues: bool,
pub auth_token: Option<String>,
pub action_timeout: Duration,
}

impl Default for Settings {
Expand Down Expand Up @@ -53,6 +54,7 @@ impl Default for Settings {
skip_errored_plugins: false,
emit_existing_issues: false,
auth_token: None,
action_timeout: Duration::from_secs(600), // Default 10 minutes
}
}
}
Expand Down
50 changes: 32 additions & 18 deletions qlty-check/src/tool/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,32 @@ impl Download {
Ok(())
}

pub fn install(&self, tool: &dyn Tool) -> Result<()> {
pub fn install(&self, tool: &dyn Tool, timeout: std::time::Duration) -> Result<()> {
let directory = PathBuf::from(tool.directory());
let tool_name = tool.name();
let mut installation = initialize_installation(tool)?;

let result = match self.file_type() {
DownloadFileType::Executable => self.install_executable(&directory, &tool_name),
DownloadFileType::Targz => self.install_targz(&directory),
DownloadFileType::Tarxz => self.install_tarxz(&directory),
DownloadFileType::Gz => self.install_gz(&directory, &tool_name),
DownloadFileType::Zip => self.install_zip(&directory),
DownloadFileType::Executable => {
self.install_executable(&directory, &tool_name, timeout)
}
DownloadFileType::Targz => self.install_targz(&directory, timeout),
DownloadFileType::Tarxz => self.install_tarxz(&directory, timeout),
DownloadFileType::Gz => self.install_gz(&directory, &tool_name, timeout),
DownloadFileType::Zip => self.install_zip(&directory, timeout),
};

finalize_installation_from_download_result(self, &mut installation, &result)?;

result
}

fn install_executable(&self, directory: &Path, tool_name: &str) -> Result<()> {
fn install_executable(
&self,
directory: &Path,
tool_name: &str,
timeout: std::time::Duration,
) -> Result<()> {
let mut binary_name = self.binary_name().unwrap_or(tool_name.to_string());

if cfg!(windows) && self.url()?.ends_with(".exe") {
Expand All @@ -129,7 +136,7 @@ impl Download {

let url = self.url().with_context(|| "Failed to get download URL")?;

let response = http::get(&url)
let response = http::get_with_timeout(&url, timeout)
.call()
.with_context(|| format!("Error downloading file from {url}"))?;

Expand Down Expand Up @@ -159,7 +166,12 @@ impl Download {
Ok(())
}

fn install_gz(&self, directory: &Path, tool_name: &str) -> Result<()> {
fn install_gz(
&self,
directory: &Path,
tool_name: &str,
timeout: std::time::Duration,
) -> Result<()> {
let binary_name = self.binary_name().unwrap_or(tool_name.to_string());
let binary_path = directory.join(binary_name);

Expand All @@ -171,7 +183,7 @@ impl Download {

let url = self.url().with_context(|| "Failed to get download URL")?;

let response = http::get(&url)
let response = http::get_with_timeout(&url, timeout)
.call()
.with_context(|| format!("Error downloading file from {url}"))?;

Expand Down Expand Up @@ -204,10 +216,10 @@ impl Download {
Ok(())
}

fn install_targz(&self, directory: &Path) -> Result<()> {
fn install_targz(&self, directory: &Path, timeout: std::time::Duration) -> Result<()> {
info!("Downloading (tar.gz) {}", self.url()?);
let url = self.url()?;
let response = http::get(&url)
let response = http::get_with_timeout(&url, timeout)
.call()
.with_context(|| format!("Error downloading file from {url}"))?;
let reader = response.into_reader();
Expand All @@ -218,10 +230,10 @@ impl Download {
Ok(())
}

fn install_tarxz(&self, directory: &Path) -> Result<()> {
fn install_tarxz(&self, directory: &Path, timeout: std::time::Duration) -> Result<()> {
info!("Downloading (tar.xz) {}", self.url()?);
let url = self.url()?;
let response = http::get(&url)
let response = http::get_with_timeout(&url, timeout)
.call()
.with_context(|| format!("Error downloading file from {url}"))?;

Expand Down Expand Up @@ -277,10 +289,11 @@ impl Download {
Ok(())
}

fn install_zip(&self, directory: &Path) -> Result<()> {
let response = http::get(&self.url()?)
fn install_zip(&self, directory: &Path, timeout: std::time::Duration) -> Result<()> {
let url = self.url()?;
let response = http::get_with_timeout(&url, timeout)
.call()
.with_context(|| format!("Error downloading file from {}", self.url().unwrap()))?;
.with_context(|| format!("Error downloading file from {}", url))?;

let mut reader = response.into_reader();
let mut file = tempfile().with_context(|| "Failed to create a temporary file")?;
Expand Down Expand Up @@ -370,6 +383,7 @@ pub struct DownloadTool {
pub plugin_name: String,
pub download: Download,
pub plugin: PluginDef,
pub timeout: std::time::Duration,
}

impl Tool for DownloadTool {
Expand Down Expand Up @@ -403,7 +417,7 @@ impl Tool for DownloadTool {

fn install(&self, task: &ProgressTask) -> Result<()> {
task.set_message(&format!("Installing {}", self.name()));
self.download.install(self)?;
self.download.install(self, self.timeout)?;

Ok(())
}
Expand Down
18 changes: 16 additions & 2 deletions qlty-check/src/tool/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,27 @@ impl GitHubRelease {
}
}

#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct GitHubReleaseTool {
pub plugin_name: String,
pub release: GitHubRelease,
pub plugin: PluginDef,
pub download: OnceCell<Download>,
pub runtime: Option<Box<dyn Tool>>,
pub timeout: std::time::Duration,
}

impl Default for GitHubReleaseTool {
fn default() -> Self {
Self {
plugin_name: String::default(),
release: GitHubRelease::default(),
plugin: PluginDef::default(),
download: OnceCell::default(),
runtime: None,
timeout: crate::settings::Settings::default().action_timeout,
}
}
}

impl Tool for GitHubReleaseTool {
Expand Down Expand Up @@ -317,7 +331,7 @@ impl Tool for GitHubReleaseTool {

fn install(&self, task: &ProgressTask) -> Result<()> {
task.set_message(&format!("Installing {}", self.name()));
self.download()?.install(self)?;
self.download()?.install(self, self.timeout)?;
Ok(())
}

Expand Down
3 changes: 2 additions & 1 deletion qlty-check/src/tool/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct Go {
pub version: String,
pub timeout: std::time::Duration,
}

impl Tool for Go {
Expand All @@ -38,7 +39,7 @@ impl Tool for Go {

fn install(&self, task: &ProgressTask) -> Result<()> {
task.set_message(&format!("Installing go v{}", self.version));
self.download().install(self)?;
self.download().install(self, self.timeout)?;
Ok(())
}

Expand Down
Loading
Loading