Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
Apply timeout to adapter execution
Archived did:key:z6MkkpTP...arsB opened 1 year ago

Resolves issue: d015633d10751674d89c9deb6fff2b44feb6a218

7 files changed +127 -5 1010100c 718e259b
modified Cargo.lock
@@ -1261,6 +1261,31 @@ dependencies = [
]

[[package]]
+
name = "num"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+
dependencies = [
+
 "num-bigint",
+
 "num-complex",
+
 "num-integer",
+
 "num-iter",
+
 "num-rational",
+
 "num-traits",
+
]
+

+
[[package]]
+
name = "num-bigint"
+
version = "0.2.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+
dependencies = [
+
 "autocfg",
+
 "num-integer",
+
 "num-traits",
+
]
+

+
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1278,6 +1303,16 @@ dependencies = [
]

[[package]]
+
name = "num-complex"
+
version = "0.2.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+
dependencies = [
+
 "autocfg",
+
 "num-traits",
+
]
+

+
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1304,6 +1339,18 @@ dependencies = [
]

[[package]]
+
name = "num-rational"
+
version = "0.2.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+
dependencies = [
+
 "autocfg",
+
 "num-bigint",
+
 "num-integer",
+
 "num-traits",
+
]
+

+
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1370,6 +1417,17 @@ dependencies = [
]

[[package]]
+
name = "parse_duration"
+
version = "2.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
+
dependencies = [
+
 "lazy_static",
+
 "num",
+
 "regex",
+
]
+

+
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1639,6 +1697,7 @@ dependencies = [
 "fehler",
 "html-page",
 "log",
+
 "parse_duration",
 "pretty_env_logger",
 "radicle",
 "radicle-git-ext",
modified Cargo.toml
@@ -27,6 +27,7 @@ thiserror = "1.0.50"
time = { version = "0.3.34", features = ["formatting", "macros"] }
uuid = { version = "1.7.0", features = ["v4"] }
regex = "1.10.4"
+
parse_duration = "2.1"

[dependencies.radicle]
version = "0.11.0"
modified README.md
@@ -75,6 +75,7 @@ db: ci-broker.sqlite
adapters:
  native:
    command: radicle-native-ci
+
    timeout: 1h
    env:
      RADICLE_NATIVE_CI: /home/liw/radicle/radicle-native-ci/x/config.yaml
    sensitive_env:
@@ -96,6 +97,7 @@ db: ci-broker.sqlite
adapters:
  native:
    command: radicle-native-ci
+
    timeout: 10m
    env:
      RADICLE_NATIVE_CI: /home/liw/radicle/radicle-native-ci/x/config.yaml
    sensitive_env:
modified ci-broker.md
@@ -19,6 +19,7 @@ default_adapter: mcadapterface
adapters:
  mcadapterface:
    command: ./adapter.sh
+
    timeout: 1h
    env:
      RADICLE_NATIVE_CI: native-ci.yaml
    sensitive_env: {}
modified src/adapter.rs
@@ -7,15 +7,19 @@
//! to something in the repository under test is expected, and not
//! considered as something going badly wrong.

+
use std::io::ErrorKind::{NotFound, PermissionDenied};
use std::{
    collections::HashMap,
    ffi::OsStr,
    io::{BufRead, BufReader, Read},
    path::{Path, PathBuf},
    process::{Command, Stdio},
+
    time::Duration,
};

use log::{debug, error};
+
use parse_duration::parse;
+
use radicle::node::DEFAULT_TIMEOUT;

use crate::{
    msg::{MessageError, Request, Response},
@@ -29,6 +33,7 @@ const NOT_EXITED: i32 = 999;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Adapter {
    bin: PathBuf,
+
    timeout: Duration,
    env: HashMap<String, String>,
}

@@ -36,10 +41,17 @@ impl Adapter {
    pub fn new(bin: &Path) -> Self {
        Self {
            bin: bin.into(),
+
            timeout: Duration::from_secs(DEFAULT_TIMEOUT.as_secs()),
            env: HashMap::new(),
        }
    }
-

+
    pub fn with_timeout(mut self, timeout_str: &str) -> Self {
+
        match parse(timeout_str) {
+
            Ok(duration) => self.timeout = duration,
+
            Err(e) => eprintln!("Failed to parse duration: {}", e),
+
        }
+
        self
+
    }
    pub fn with_environment(mut self, env: &HashMap<String, String>) -> Self {
        for (key, value) in env.iter() {
            self.env.insert(key.into(), value.into());
@@ -74,9 +86,18 @@ impl Adapter {
    ) -> Result<(), AdapterError> {
        assert!(matches!(trigger, Request::Trigger { .. }));

-
        // Spawn the adapter sub-process.
+
        let timeout_cmd = if std::env::consts::OS == "macos" {
+
            "gtimeout"
+
        } else {
+
            "timeout"
+
        };
+

        debug!("spawn adapter sub-process");
-
        let mut child = Command::new(&self.bin)
+
        let timeout_duration_secs = self.timeout.as_secs().to_string();
+

+
        let mut child = Command::new(timeout_cmd)
+
            .arg(timeout_duration_secs)
+
            .arg(&self.bin)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
@@ -156,7 +177,23 @@ impl Adapter {
                    .map_err(AdapterError::ReadStderr)?;
                let stderr = String::from_utf8_lossy(&buf);
                debug!("adapter stderr: {stderr:?}");
-
                return Err(AdapterError::Failed(exit));
+
                return match exit {
+
                    127 => {
+
                        // Not found
+
                        Err(AdapterError::SpawnAdapter(
+
                            self.bin.clone(),
+
                            std::io::Error::from(NotFound),
+
                        ))
+
                    }
+
                    126 => {
+
                        // Permission denied
+
                        Err(AdapterError::SpawnAdapter(
+
                            self.bin.clone(),
+
                            std::io::Error::from(PermissionDenied),
+
                        ))
+
                    }
+
                    _ => Err(AdapterError::Failed(exit)),
+
                };
            }
        } else {
            error!("adapter sub-process did not exit voluntarily");
modified src/bin/ci-broker.rs
@@ -69,6 +69,7 @@ fn fallible_main() -> Result<(), BrokerError> {
                config.default_adapter.clone(),
            ))?;
    let adapter = Adapter::new(&spec.command)
+
        .with_timeout(spec.timeout_as_duration().as_secs().to_string().as_str())
        .with_environment(spec.envs())
        .with_environment(spec.sensitive_envs());
    broker.set_default_adapter(&adapter);
modified src/config.rs
@@ -1,17 +1,22 @@
//! Configuration for the CI broker and related programs.

+
use std::string::ToString;
use std::{
    collections::HashMap,
    fmt,
    path::{Path, PathBuf},
};

+
use parse_duration::parse;
use serde::{Deserialize, Serialize};
+
use std::time::Duration;

use crate::event::EventFilter;

const DEFAULT_STATUS_PAGE_UPDATE_INTERVAL: u64 = 10;

+
const DEFAULT_ADAPTER_TIMEOUT_MINUTES: u64 = 60;
+

#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
    pub default_adapter: String,
@@ -46,8 +51,9 @@ impl fmt::Debug for Adapter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
-
            "Adapter {{ \n command: {:#?}, \n env: {:#?}, \n sensitive_env: {:#?} }}",
+
            "Adapter {{ \n command: {:#?}, \n timeout: {:#?}, \n env: {:#?}, \n sensitive_env: {:#?} }}",
            self.command,
+
            self.timeout,
            self.env,
            self.sensitive_env
                .keys()
@@ -60,11 +66,26 @@ impl fmt::Debug for Adapter {
#[derive(Serialize, Deserialize)]
pub struct Adapter {
    pub command: PathBuf,
+
    pub timeout: Option<String>,
    pub env: HashMap<String, String>,
    pub sensitive_env: HashMap<String, String>,
}

impl Adapter {
+
    pub fn with_timeout(mut self, timeout: Option<&str>) -> Self {
+
        self.timeout = timeout.map(|t| t.to_string());
+
        self
+
    }
+
    pub fn timeout_as_duration(&self) -> Duration {
+
        self.timeout
+
            .as_ref()
+
            .and_then(|t| parse(t).ok())
+
            .unwrap_or_else(|| Duration::from_secs(DEFAULT_ADAPTER_TIMEOUT_MINUTES * 60))
+
    }
+
    pub fn timeout(&self) -> Option<&str> {
+
        self.timeout.as_deref()
+
    }
+

    pub fn envs(&self) -> &HashMap<String, String> {
        &self.env
    }