runc-shim: fix leaking thread and fd

The stdin fifo fd should be closed when containerd
calls close io, otherwise, runc-shim would hold
this fd and the copy console thread forever.

Signed-off-by: Zhang Tianyang <burning9699@gmail.com>
This commit is contained in:
Zhang Tianyang 2022-12-29 15:04:59 +08:00
parent 3197c5ed4d
commit ffa56dbf3b
7 changed files with 110 additions and 34 deletions

View File

@ -22,7 +22,7 @@ use std::{
}, },
path::{Path, PathBuf}, path::{Path, PathBuf},
process::ExitStatus, process::ExitStatus,
sync::Arc, sync::{Arc, Mutex},
}; };
use async_trait::async_trait; use async_trait::async_trait;
@ -241,6 +241,7 @@ impl ProcessFactory<ExecProcess> for RuncExecFactory {
spec: p, spec: p,
exit_signal: Default::default(), exit_signal: Default::default(),
}), }),
stdin: Arc::new(Mutex::new(None)),
}) })
} }
} }
@ -402,6 +403,24 @@ impl ProcessLifecycle<ExecProcess> for RuncExecLifecycle {
} }
return Err(runtime_error(&bundle, e, "OCI runtime exec failed").await); return Err(runtime_error(&bundle, e, "OCI runtime exec failed").await);
} }
if !p.stdio.stdin.is_empty() {
let stdin_clone = p.stdio.stdin.clone();
let stdin_w = p.stdin.clone();
// Open the write side in advance to make sure read side will not block,
// open it in another thread otherwise it will block too.
tokio::spawn(async move {
if let Ok(stdin_w_file) = OpenOptions::new()
.write(true)
.open(stdin_clone.as_str())
.await
{
let mut lock_guard = stdin_w.lock().unwrap();
*lock_guard = Some(stdin_w_file);
}
});
}
copy_io_or_console(p, socket, pio, p.lifecycle.exit_signal.clone()).await?; copy_io_or_console(p, socket, pio, p.lifecycle.exit_signal.clone()).await?;
let pid = read_file_to_str(pid_path).await?.parse::<i32>()?; let pid = read_file_to_str(pid_path).await?.parse::<i32>()?;
p.pid = pid; p.pid = pid;
@ -466,28 +485,12 @@ async fn copy_console(
.try_clone() .try_clone()
.await .await
.map_err(io_error!(e, "failed to clone console file"))?; .map_err(io_error!(e, "failed to clone console file"))?;
let stdin_fut = async { let stdin = OpenOptions::new()
OpenOptions::new() .read(true)
.read(true) .open(stdio.stdin.as_str())
.open(stdio.stdin.as_str()) .await
.await .map_err(io_error!(e, "failed to open stdin"))?;
}; spawn_copy(stdin, console_stdin, exit_signal.clone(), None::<fn()>);
let stdin_w_fut = async {
OpenOptions::new()
.write(true)
.open(stdio.stdin.as_str())
.await
};
let (stdin, stdin_w) =
tokio::try_join!(stdin_fut, stdin_w_fut).map_err(io_error!(e, "open stdin"))?;
spawn_copy(
stdin,
console_stdin,
exit_signal.clone(),
Some(move || {
drop(stdin_w);
}),
);
} }
if !stdio.stdout.is_empty() { if !stdio.stdout.is_empty() {

View File

@ -20,7 +20,10 @@ use std::{
fs::{File, OpenOptions}, fs::{File, OpenOptions},
os::unix::io::{AsRawFd, FromRawFd}, os::unix::io::{AsRawFd, FromRawFd},
path::Path, path::Path,
sync::mpsc::{sync_channel, Receiver, SyncSender}, sync::{
mpsc::{sync_channel, Receiver, SyncSender},
Arc, Mutex,
},
}; };
use containerd_shim as shim; use containerd_shim as shim;
@ -65,6 +68,7 @@ pub trait Process {
fn copy_io(&self) -> Result<()>; fn copy_io(&self) -> Result<()>;
fn set_pid_from_file(&mut self, pid_path: &Path) -> Result<()>; fn set_pid_from_file(&mut self, pid_path: &Path) -> Result<()>;
fn resize_pty(&mut self, height: u32, width: u32) -> Result<()>; fn resize_pty(&mut self, height: u32, width: u32) -> Result<()>;
fn close_io(&self) -> Result<()>;
} }
pub trait Container { pub trait Container {
@ -81,6 +85,7 @@ pub trait Container {
fn update(&mut self, resources: &LinuxResources) -> Result<()>; fn update(&mut self, resources: &LinuxResources) -> Result<()>;
fn pids(&self) -> Result<PidsResponse>; fn pids(&self) -> Result<PidsResponse>;
fn id(&self) -> String; fn id(&self) -> String;
fn close_io(&mut self, exec_id: Option<&str>) -> Result<()>;
} }
pub struct CommonContainer<T, E> { pub struct CommonContainer<T, E> {
@ -178,6 +183,7 @@ pub struct CommonProcess {
pub exited_at: Option<OffsetDateTime>, pub exited_at: Option<OffsetDateTime>,
pub wait_chan_tx: Vec<SyncSender<i8>>, pub wait_chan_tx: Vec<SyncSender<i8>>,
pub console: Option<Console>, pub console: Option<Console>,
pub stdin: Arc<Mutex<Option<File>>>,
} }
impl Process for CommonProcess { impl Process for CommonProcess {
@ -260,7 +266,6 @@ impl Process for CommonProcess {
let f = unsafe { File::from_raw_fd(fd) }; let f = unsafe { File::from_raw_fd(fd) };
let stdin = OpenOptions::new() let stdin = OpenOptions::new()
.read(true) .read(true)
.write(true)
.open(self.stdio.stdin.as_str()) .open(self.stdio.stdin.as_str())
.map_err(io_error!(e, "open stdin"))?; .map_err(io_error!(e, "open stdin"))?;
spawn_copy_for_tty(stdin, f, None, None); spawn_copy_for_tty(stdin, f, None, None);
@ -323,4 +328,12 @@ impl Process for CommonProcess {
None => Err(other!("there is no console")), None => Err(other!("there is no console")),
} }
} }
fn close_io(&self) -> Result<()> {
let mut lock_guard = self.stdin.lock().unwrap();
if let Some(stdin_w) = lock_guard.take() {
drop(stdin_w);
}
Ok(())
}
} }

View File

@ -17,14 +17,14 @@
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
fs::{remove_file, File}, fs::{remove_file, File, OpenOptions},
io::{BufRead, BufReader, Read}, io::{BufRead, BufReader, Read},
os::unix::prelude::ExitStatusExt, os::unix::prelude::ExitStatusExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::ExitStatus, process::ExitStatus,
sync::{ sync::{
mpsc::{Receiver, SyncSender}, mpsc::{Receiver, SyncSender},
Arc, Arc, Mutex,
}, },
}; };
@ -203,7 +203,23 @@ impl Container for RuncContainer {
.init .init
.runtime .runtime
.exec(&id, &process.spec, Some(&exec_opts)) .exec(&id, &process.spec, Some(&exec_opts))
.map_err(|e| runtime_error(&bundle, e, "OCI runtime exec failed"))?; .map_err(other_error!(e, "failed exec"))?;
if !process.common.stdio.stdin.is_empty() {
let stdin_clone = process.common.stdio.stdin.clone();
let stdin_w = process.common.stdin.clone();
// Open the write side in advance to make sure read side will not block,
// open it in another thread otherwise it will block too.
std::thread::spawn(move || {
if let Ok(stdin_w_file) =
OpenOptions::new().write(true).open(stdin_clone.as_str())
{
let mut lock_guard = stdin_w.lock().unwrap();
*lock_guard = Some(stdin_w_file);
}
});
}
if process.common.stdio.terminal { if process.common.stdio.terminal {
let console_socket = let console_socket =
socket.ok_or_else(|| other!("failed to get console socket"))?; socket.ok_or_else(|| other!("failed to get console socket"))?;
@ -368,6 +384,11 @@ impl Container for RuncContainer {
fn id(&self) -> String { fn id(&self) -> String {
self.common.id.to_string() self.common.id.to_string()
} }
fn close_io(&mut self, exec_id: Option<&str>) -> Result<()> {
let p = self.common.get_mut_process(exec_id)?;
p.close_io()
}
} }
impl RuncContainer { impl RuncContainer {
@ -424,6 +445,7 @@ impl InitProcess {
exited_at: None, exited_at: None,
wait_chan_tx: vec![], wait_chan_tx: vec![],
console: None, console: None,
stdin: Arc::new(Mutex::new(None)),
}, },
bundle: bundle.to_string(), bundle: bundle.to_string(),
runtime, runtime,
@ -548,6 +570,10 @@ impl Process for InitProcess {
fn resize_pty(&mut self, height: u32, width: u32) -> Result<()> { fn resize_pty(&mut self, height: u32, width: u32) -> Result<()> {
self.common.resize_pty(height, width) self.common.resize_pty(height, width)
} }
fn close_io(&self) -> Result<()> {
self.common.close_io()
}
} }
pub(crate) struct ExecProcess { pub(crate) struct ExecProcess {
@ -623,6 +649,10 @@ impl Process for ExecProcess {
fn resize_pty(&mut self, height: u32, width: u32) -> Result<()> { fn resize_pty(&mut self, height: u32, width: u32) -> Result<()> {
self.common.resize_pty(height, width) self.common.resize_pty(height, width)
} }
fn close_io(&self) -> Result<()> {
self.common.close_io()
}
} }
impl TryFrom<ExecProcessRequest> for ExecProcess { impl TryFrom<ExecProcessRequest> for ExecProcess {
@ -645,6 +675,7 @@ impl TryFrom<ExecProcessRequest> for ExecProcess {
exited_at: None, exited_at: None,
wait_chan_tx: vec![], wait_chan_tx: vec![],
console: None, console: None,
stdin: Arc::new(Mutex::new(None)),
}, },
spec: p, spec: p,
}; };

View File

@ -262,8 +262,13 @@ where
Ok(Empty::new()) Ok(Empty::new())
} }
fn close_io(&self, _ctx: &TtrpcContext, _req: CloseIORequest) -> TtrpcResult<Empty> { fn close_io(&self, _ctx: &TtrpcContext, req: CloseIORequest) -> TtrpcResult<Empty> {
// unnecessary close io here since fd was closed automatically after object was destroyed. let mut containers = self.containers.lock().unwrap();
let container = containers
.get_mut(&req.id)
.ok_or_else(|| Error::Other(format!("can not find container by id {}", &req.id)))?;
let exec_id_opt = req.exec_id().none_if(|x| x.is_empty());
container.close_io(exec_id_opt)?;
Ok(Empty::new()) Ok(Empty::new())
} }

View File

@ -49,6 +49,7 @@ pub trait Container {
async fn update(&mut self, resources: &LinuxResources) -> Result<()>; async fn update(&mut self, resources: &LinuxResources) -> Result<()>;
async fn stats(&self) -> Result<Metrics>; async fn stats(&self) -> Result<Metrics>;
async fn all_processes(&self) -> Result<Vec<ProcessInfo>>; async fn all_processes(&self) -> Result<Vec<ProcessInfo>>;
async fn close_io(&mut self, exec_id: Option<&str>) -> Result<()>;
} }
#[async_trait] #[async_trait]
@ -182,6 +183,11 @@ where
async fn all_processes(&self) -> Result<Vec<ProcessInfo>> { async fn all_processes(&self) -> Result<Vec<ProcessInfo>> {
self.init.ps().await self.init.ps().await
} }
async fn close_io(&mut self, exec_id: Option<&str>) -> Result<()> {
let process = self.get_mut_process(exec_id)?;
process.close_io().await
}
} }
impl<T, E, P> ContainerTemplate<T, E, P> impl<T, E, P> ContainerTemplate<T, E, P>

View File

@ -14,7 +14,10 @@
limitations under the License. limitations under the License.
*/ */
use std::{os::unix::io::AsRawFd, sync::Arc}; use std::{
os::unix::io::AsRawFd,
sync::{Arc, Mutex},
};
use async_trait::async_trait; use async_trait::async_trait;
use containerd_shim_protos::{ use containerd_shim_protos::{
@ -24,7 +27,10 @@ use containerd_shim_protos::{
}; };
use oci_spec::runtime::LinuxResources; use oci_spec::runtime::LinuxResources;
use time::OffsetDateTime; use time::OffsetDateTime;
use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::{
fs::File,
sync::oneshot::{channel, Receiver, Sender},
};
use crate::{io::Stdio, ioctl_set_winsz, util::asyncify, Console}; use crate::{io::Stdio, ioctl_set_winsz, util::asyncify, Console};
@ -43,6 +49,7 @@ pub trait Process {
async fn update(&mut self, resources: &LinuxResources) -> crate::Result<()>; async fn update(&mut self, resources: &LinuxResources) -> crate::Result<()>;
async fn stats(&self) -> crate::Result<Metrics>; async fn stats(&self) -> crate::Result<Metrics>;
async fn ps(&self) -> crate::Result<Vec<ProcessInfo>>; async fn ps(&self) -> crate::Result<Vec<ProcessInfo>>;
async fn close_io(&mut self) -> crate::Result<()>;
} }
#[async_trait] #[async_trait]
@ -65,6 +72,7 @@ pub struct ProcessTemplate<S> {
pub wait_chan_tx: Vec<Sender<()>>, pub wait_chan_tx: Vec<Sender<()>>,
pub console: Option<Console>, pub console: Option<Console>,
pub lifecycle: Arc<S>, pub lifecycle: Arc<S>,
pub stdin: Arc<Mutex<Option<File>>>,
} }
impl<S> ProcessTemplate<S> { impl<S> ProcessTemplate<S> {
@ -79,6 +87,7 @@ impl<S> ProcessTemplate<S> {
wait_chan_tx: vec![], wait_chan_tx: vec![],
console: None, console: None,
lifecycle: Arc::new(lifecycle), lifecycle: Arc::new(lifecycle),
stdin: Arc::new(Mutex::new(None)),
} }
} }
} }
@ -176,4 +185,12 @@ where
async fn ps(&self) -> crate::Result<Vec<ProcessInfo>> { async fn ps(&self) -> crate::Result<Vec<ProcessInfo>> {
self.lifecycle.ps(self).await self.lifecycle.ps(self).await
} }
async fn close_io(&mut self) -> crate::Result<()> {
let mut lock_guard = self.stdin.lock().unwrap();
if let Some(stdin_w_file) = lock_guard.take() {
drop(stdin_w_file);
}
Ok(())
}
} }

View File

@ -269,8 +269,9 @@ where
Ok(Empty::new()) Ok(Empty::new())
} }
async fn close_io(&self, _ctx: &TtrpcContext, _req: CloseIORequest) -> TtrpcResult<Empty> { async fn close_io(&self, _ctx: &TtrpcContext, req: CloseIORequest) -> TtrpcResult<Empty> {
// TODO call close_io of container let mut container = self.get_container(req.id()).await?;
container.close_io(req.exec_id().as_option()).await?;
Ok(Empty::new()) Ok(Empty::new())
} }