blob: d8e4ba5a79d2fbebe99a18596f69fc3b7d0e29b1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::Spawned;
use crate::{
Command,
windows::{job::Job, pipe, wait_process},
};
use std::{io, os::windows::io::OwnedHandle, process::ExitStatus, time::Duration};
pub struct Child {
process: OwnedHandle,
job: Job,
pid: u32,
status: Option<ExitStatus>,
cleaned: bool,
runner: Option<crate::bootstrap::Runner>,
}
pub async fn spawn(plan: Command) -> Result<Spawned, maka_agent_facts::tools::ToolError> {
spawn_prepared(plan.prepare().await?).await
}
pub(crate) async fn spawn_prepared(
mut plan: crate::command::Prepared,
) -> Result<Spawned, maka_agent_facts::tools::ToolError> {
if let Some(launch) = plan.take_launch() {
return launch
.runner
.pipes(launch.endpoint, plan, launch.capabilities)
.await
.map_err(crate::launch_error);
}
let result: io::Result<_> = async {
let (stdin, stdin_peer) = pipe::input().await?;
let (stdout, stdout_peer) = pipe::output().await?;
let (stderr, stderr_peer) = pipe::output().await?;
let job = Job::new()?;
let (process, pid) = crate::windows::spawn::launch(
&plan,
&job,
[&stdin_peer, &stdout_peer, &stderr_peer],
crate::windows::spawn::Console::None,
)?;
Ok(Spawned {
stdin,
stdout,
stderr,
child: Child {
process,
job,
pid,
status: None,
cleaned: false,
runner: None,
},
})
}
.await;
result.map_err(crate::failed)
}
impl Child {
pub(crate) fn from_runner(
process: OwnedHandle,
pid: u32,
job: Job,
runner: crate::bootstrap::Runner,
) -> Self {
Self {
process,
pid,
job,
status: None,
cleaned: false,
runner: Some(runner),
}
}
pub fn id(&self) -> u32 {
self.pid
}
pub fn terminate(&mut self) -> io::Result<()> {
self.job.terminate(130)
}
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
if self.status.is_none() {
self.status = Some(wait_process(&self.process).await?);
}
if !self.cleaned {
self.terminate()?;
tokio::time::timeout(Duration::from_secs(2), async {
while !self.job.is_empty()? {
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<_, io::Error>(())
})
.await
.map_err(|_| io::Error::other("process Job exit is unconfirmed"))??;
self.cleaned = true;
}
if let Some(runner) = &mut self.runner {
runner.finish().await?;
}
Ok(self.status.expect("root exited"))
}
}
// Job's kill-on-close is emergency cleanup. Only wait() confirms tree exit.