host: wasip1 processes, wasi:http, identity, stdio options, services (T12)
- Core modules run on the host-side preview1 over the shared VFS (D41). - wasi:http default handler in the linker; anonymous HTTPS git clone works. Sockets stay denied until P2. - Each process carries the conversation (from Exec, inherited by children, shown in /proc/processes) and a host-assigned SAUSAGE_PID. - Spawn requests choose pipe, inherit or null per stream. - /config/services.yaml supervisor: live edits, restarts, crash limit 5/10 s, graceful stop (stdin EOF, then kill after 5 s) (D44). - Compile caches keyed by content; PATH=/usr/local/bin:/bin. world.rs also carries T13's optional read-only /bin mount (D43).
This commit is contained in:
+145
-38
@@ -1,4 +1,4 @@
|
||||
//! Runs components in independent stores over the world root. Owns process groups, stdio, and Exec.
|
||||
//! Runs WASI programs in independent stores over the world root. Owns process groups, stdio, and Exec.
|
||||
use crate::{
|
||||
exec::{Command, Exec, Options, Stream},
|
||||
world::World,
|
||||
@@ -26,7 +26,7 @@ use wasmtime::{
|
||||
};
|
||||
use wasmtime_wasi::{
|
||||
WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView,
|
||||
cli::{AsyncStdinStream, AsyncStdoutStream},
|
||||
cli::AsyncStdoutStream,
|
||||
p2::{
|
||||
DynInputStream, DynOutputStream,
|
||||
bindings::CommandPre,
|
||||
@@ -44,7 +44,17 @@ mod bindings {
|
||||
},
|
||||
});
|
||||
}
|
||||
use bindings::sausage::process::host::{Host, HostChild, Request};
|
||||
use bindings::sausage::process::host::{Host, HostChild, Request, Stdio};
|
||||
mod services;
|
||||
mod stdio;
|
||||
pub use services::supervise;
|
||||
use stdio::Io;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Compiled {
|
||||
Component(CommandPre<State>),
|
||||
Module(wasmtime::InstancePre<State>),
|
||||
}
|
||||
|
||||
/// A finite snapshot of process metadata exposed through /proc/processes.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -53,6 +63,7 @@ pub struct ProcessInfo {
|
||||
pub argv: Vec<String>,
|
||||
pub cwd: String,
|
||||
pub state: String,
|
||||
pub conversation: Option<String>,
|
||||
}
|
||||
pub type Processes = Arc<Mutex<std::collections::BTreeMap<u32, ProcessInfo>>>;
|
||||
|
||||
@@ -66,7 +77,7 @@ struct Entry {
|
||||
pub struct Runtime {
|
||||
world: World,
|
||||
engine: Engine,
|
||||
cache: Mutex<HashMap<Vec<u8>, CommandPre<State>>>,
|
||||
cache: Mutex<HashMap<Vec<u8>, Compiled>>,
|
||||
entries: Mutex<HashMap<u32, Entry>>,
|
||||
next: AtomicU32,
|
||||
alive: Arc<AtomicBool>,
|
||||
@@ -84,6 +95,19 @@ struct State {
|
||||
pid: u32,
|
||||
command: u32,
|
||||
group: u32,
|
||||
conversation: Option<String>,
|
||||
stdio: Io,
|
||||
preview1: Option<sausage_vfs::preview1::State>,
|
||||
http: wasmtime_wasi_http::WasiHttpCtx,
|
||||
}
|
||||
impl wasmtime_wasi_http::WasiHttpView for State {
|
||||
fn http(&mut self) -> wasmtime_wasi_http::WasiHttpCtxView<'_> {
|
||||
wasmtime_wasi_http::WasiHttpCtxView {
|
||||
ctx: &mut self.http,
|
||||
table: &mut self.table,
|
||||
hooks: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WasiView for State {
|
||||
fn ctx(&mut self) -> WasiCtxView<'_> {
|
||||
@@ -105,15 +129,17 @@ struct Started {
|
||||
pid: u32,
|
||||
group: u32,
|
||||
code: watch::Receiver<Option<i32>>,
|
||||
input: DuplexStream,
|
||||
output: DuplexStream,
|
||||
error: DuplexStream,
|
||||
input: Option<DuplexStream>,
|
||||
output: Option<DuplexStream>,
|
||||
error: Option<DuplexStream>,
|
||||
}
|
||||
impl Runtime {
|
||||
pub fn open(path: &Path) -> anyhow::Result<Arc<Self>> {
|
||||
Self::new(World::open(path)?)
|
||||
}
|
||||
pub fn new(world: World) -> anyhow::Result<Arc<Self>> {
|
||||
// Deno and WASI HTTP enable different providers. Select one before HTTPS creates a client.
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.epoch_interruption(true);
|
||||
let mut cache = wasmtime::CacheConfig::new();
|
||||
@@ -138,6 +164,10 @@ impl Runtime {
|
||||
next: AtomicU32::new(1),
|
||||
}))
|
||||
}
|
||||
/// Number of distinct program contents compiled by this runtime.
|
||||
pub fn compiled_programs(&self) -> usize {
|
||||
self.cache.lock().unwrap().len()
|
||||
}
|
||||
fn kill_command(&self, command: u32) {
|
||||
for entry in self.entries.lock().unwrap().values() {
|
||||
if entry.command == command {
|
||||
@@ -170,6 +200,8 @@ impl Runtime {
|
||||
parent: u32,
|
||||
command: u32,
|
||||
inherited_group: u32,
|
||||
conversation: Option<String>,
|
||||
inherited_io: Option<Io>,
|
||||
) -> anyhow::Result<Started> {
|
||||
anyhow::ensure!(!request.argv.is_empty(), "empty argv");
|
||||
let cancelled = if parent == 0 {
|
||||
@@ -182,6 +214,11 @@ impl Runtime {
|
||||
.map(|entry| entry.cancelled.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("parent exited"))?
|
||||
};
|
||||
if !request.env.iter().any(|(k, _)| k == "PATH") {
|
||||
request
|
||||
.env
|
||||
.push(("PATH".into(), "/usr/local/bin:/bin".into()));
|
||||
}
|
||||
request.cwd = sausage_vfs::resolve("/", &request.cwd);
|
||||
request.env.retain(|(name, _)| name != "PWD");
|
||||
request.env.push(("PWD".into(), request.cwd.clone()));
|
||||
@@ -205,19 +242,29 @@ impl Runtime {
|
||||
if let Some(pre) = cache.get(&bytes) {
|
||||
pre.clone()
|
||||
} else {
|
||||
let component = Component::new(&self.engine, &bytes)?;
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
|
||||
// The library linker includes exit-with-code. Only Wasmtime's CLI gates it.
|
||||
adapter::add_to_linker(&mut linker, |s: &mut State| View {
|
||||
adapter: &mut s.fs,
|
||||
table: &mut s.table,
|
||||
})?;
|
||||
bindings::sausage::process::host::add_to_linker::<
|
||||
_,
|
||||
wasmtime::component::HasSelf<_>,
|
||||
>(&mut linker, |s| s)?;
|
||||
let pre = CommandPre::new(linker.instantiate_pre(&component)?)?;
|
||||
let pre = if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
|
||||
let module = wasmtime::Module::new(&self.engine, &bytes)?;
|
||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
|
||||
s.preview1.as_mut().unwrap()
|
||||
})?;
|
||||
Compiled::Module(linker.instantiate_pre(&module)?)
|
||||
} else {
|
||||
let component = Component::new(&self.engine, &bytes)?;
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
|
||||
// The library linker includes exit-with-code. Only Wasmtime's CLI gates it.
|
||||
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
|
||||
adapter::add_to_linker(&mut linker, |s: &mut State| View {
|
||||
adapter: &mut s.fs,
|
||||
table: &mut s.table,
|
||||
})?;
|
||||
bindings::sausage::process::host::add_to_linker::<
|
||||
_,
|
||||
wasmtime::component::HasSelf<_>,
|
||||
>(&mut linker, |s| s)?;
|
||||
Compiled::Component(CommandPre::new(linker.instantiate_pre(&component)?)?)
|
||||
};
|
||||
cache.insert(bytes, pre.clone());
|
||||
pre
|
||||
}
|
||||
@@ -230,20 +277,45 @@ impl Runtime {
|
||||
None if inherited_group != 0 => inherited_group,
|
||||
None => pid,
|
||||
};
|
||||
request.env.retain(|(name, _)| name != "SAUSAGE_PID");
|
||||
request.env.push(("SAUSAGE_PID".into(), pid.to_string()));
|
||||
request.env.push(("PID".into(), pid.to_string()));
|
||||
request.env.push(("PPID".into(), parent.to_string()));
|
||||
let (input, child_input) = tokio::io::duplex(8192);
|
||||
let (output, child_output) = tokio::io::duplex(8192);
|
||||
let (error, child_error) = tokio::io::duplex(8192);
|
||||
let (input, child_input) = stdio::input(request.stdin, inherited_io.as_ref());
|
||||
let (output, child_output) =
|
||||
stdio::output(request.stdout, inherited_io.as_ref().map(|io| &io.output));
|
||||
let (error, child_error) =
|
||||
stdio::output(request.stderr, inherited_io.as_ref().map(|io| &io.error));
|
||||
let stdio = Io {
|
||||
input: child_input,
|
||||
output: child_output,
|
||||
error: child_error,
|
||||
};
|
||||
let preview1 = if matches!(pre, Compiled::Module(_)) {
|
||||
Some(sausage_vfs::preview1::State::new(
|
||||
self.world.root.clone(),
|
||||
&request.cwd,
|
||||
request.argv.clone(),
|
||||
request.env.clone(),
|
||||
stdio.input(),
|
||||
Box::new(stdio.output.clone()),
|
||||
Box::new(stdio.error.clone()),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut mounts = Mounts::default();
|
||||
mounts.mount("/", self.world.root.clone(), false);
|
||||
let mut wasi = WasiCtxBuilder::new();
|
||||
wasi.args(&request.argv)
|
||||
.envs(&request.env)
|
||||
.initial_cwd(&request.cwd)
|
||||
.stdin(AsyncStdinStream::new(child_input))
|
||||
.stdout(AsyncStdoutStream::new(8192, child_output))
|
||||
.stderr(AsyncStdoutStream::new(8192, child_error));
|
||||
.stdin(stdio.input.clone())
|
||||
.stdout(AsyncStdoutStream::new(8192, stdio.output.clone()))
|
||||
.stderr(AsyncStdoutStream::new(8192, stdio.error.clone()))
|
||||
.allow_tcp(false)
|
||||
.allow_udp(false)
|
||||
.allow_ip_name_lookup(false);
|
||||
let mut store = Store::new(
|
||||
&self.engine,
|
||||
State {
|
||||
@@ -254,6 +326,10 @@ impl Runtime {
|
||||
pid,
|
||||
command,
|
||||
group,
|
||||
conversation: conversation.clone(),
|
||||
stdio,
|
||||
preview1,
|
||||
http: wasmtime_wasi_http::WasiHttpCtx::new(),
|
||||
},
|
||||
);
|
||||
store.epoch_deadline_async_yield_and_update(1);
|
||||
@@ -264,6 +340,7 @@ impl Runtime {
|
||||
argv: request.argv,
|
||||
cwd: request.cwd,
|
||||
state: "running".into(),
|
||||
conversation,
|
||||
},
|
||||
);
|
||||
let (tx, code) = watch::channel(None);
|
||||
@@ -281,8 +358,21 @@ impl Runtime {
|
||||
return;
|
||||
}
|
||||
let result = async {
|
||||
let command = pre.instantiate_async(&mut store).await?;
|
||||
match command.wasi_cli_run().call_run(&mut store).await {
|
||||
let result = match pre {
|
||||
Compiled::Component(pre) => {
|
||||
let command = pre.instantiate_async(&mut store).await?;
|
||||
command.wasi_cli_run().call_run(&mut store).await
|
||||
}
|
||||
Compiled::Module(pre) => {
|
||||
let module = pre.instantiate_async(&mut store).await?;
|
||||
module
|
||||
.get_typed_func::<(), ()>(&mut store, "_start")?
|
||||
.call_async(&mut store, ())
|
||||
.await
|
||||
.map(|()| Ok(()))
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Ok(Ok(())) => Ok(0),
|
||||
Ok(Err(())) => Ok(1),
|
||||
Err(e) => match e.downcast_ref::<wasmtime_wasi::I32Exit>() {
|
||||
@@ -348,7 +438,14 @@ impl Host for State {
|
||||
) -> wasmtime::Result<Result<Resource<Child>, String>> {
|
||||
let child = match self
|
||||
.runtime
|
||||
.start(request, self.pid, self.command, self.group)
|
||||
.start(
|
||||
request,
|
||||
self.pid,
|
||||
self.command,
|
||||
self.group,
|
||||
self.conversation.clone(),
|
||||
Some(self.stdio.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(child) => child,
|
||||
@@ -358,9 +455,15 @@ impl Host for State {
|
||||
pid: child.pid,
|
||||
group: child.group,
|
||||
code: child.code,
|
||||
input: Some(Box::new(AsyncWriteStream::new(8192, child.input))),
|
||||
output: Some(Box::new(AsyncReadStream::new(child.output))),
|
||||
error: Some(Box::new(AsyncReadStream::new(child.error))),
|
||||
input: child
|
||||
.input
|
||||
.map(|s| Box::new(AsyncWriteStream::new(8192, s)) as DynOutputStream),
|
||||
output: child
|
||||
.output
|
||||
.map(|s| Box::new(AsyncReadStream::new(s)) as DynInputStream),
|
||||
error: child
|
||||
.error
|
||||
.map(|s| Box::new(AsyncReadStream::new(s)) as DynInputStream),
|
||||
})?))
|
||||
}
|
||||
async fn kill(&mut self, pid: u32) -> wasmtime::Result<Result<(), String>> {
|
||||
@@ -463,10 +566,7 @@ impl Exec for Arc<Runtime> {
|
||||
],
|
||||
Command::Argv(argv) => argv,
|
||||
};
|
||||
let mut env = options.env;
|
||||
if !env.iter().any(|(k, _)| k == "PATH") {
|
||||
env.push(("PATH".into(), "/bin".into()));
|
||||
}
|
||||
let env = options.env;
|
||||
let start = self.start(
|
||||
Request {
|
||||
program: argv.first().cloned().unwrap_or_default(),
|
||||
@@ -474,10 +574,15 @@ impl Exec for Arc<Runtime> {
|
||||
env,
|
||||
cwd: options.cwd,
|
||||
group: None,
|
||||
stdin: Stdio::Pipe,
|
||||
stdout: Stdio::Pipe,
|
||||
stderr: Stdio::Pipe,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
options.conversation,
|
||||
None,
|
||||
);
|
||||
let mut child = if let Some(deadline) = deadline {
|
||||
tokio::time::timeout_at(deadline, start)
|
||||
@@ -491,13 +596,15 @@ impl Exec for Arc<Runtime> {
|
||||
group: child.pid,
|
||||
};
|
||||
drop(child.input);
|
||||
let mut stdout = child.output.take().unwrap();
|
||||
let mut stderr = child.error.take().unwrap();
|
||||
let run = async {
|
||||
let (mut out_open, mut err_open) = (true, true);
|
||||
let (mut out, mut err) = ([0; 8192], [0; 8192]);
|
||||
while out_open || err_open {
|
||||
tokio::select! {
|
||||
n = child.output.read(&mut out), if out_open => { let n = n?; out_open = n != 0; if n != 0 { output(Stream::Stdout, &out[..n]); } }
|
||||
n = child.error.read(&mut err), if err_open => { let n = n?; err_open = n != 0; if n != 0 { output(Stream::Stderr, &err[..n]); } }
|
||||
n = stdout.read(&mut out), if out_open => { let n = n?; out_open = n != 0; if n != 0 { output(Stream::Stdout, &out[..n]); } }
|
||||
n = stderr.read(&mut err), if err_open => { let n = n?; err_open = n != 0; if n != 0 { output(Stream::Stderr, &err[..n]); } }
|
||||
}
|
||||
}
|
||||
loop {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
//! Supervises the world's services list, live edits, crash limits, and graceful stops.
|
||||
use super::{GroupGuard, Request, Runtime, Started, Stdio};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
struct Spec {
|
||||
name: String,
|
||||
argv: Vec<String>,
|
||||
#[serde(default = "root")]
|
||||
cwd: String,
|
||||
#[serde(default)]
|
||||
env: BTreeMap<String, String>,
|
||||
}
|
||||
fn root() -> String {
|
||||
"/".into()
|
||||
}
|
||||
struct Live {
|
||||
child: Started,
|
||||
_guard: GroupGuard,
|
||||
}
|
||||
struct Service {
|
||||
spec: Spec,
|
||||
live: Option<Live>,
|
||||
crashes: VecDeque<Instant>,
|
||||
stopped: bool,
|
||||
}
|
||||
impl Service {
|
||||
async fn start(&mut self, runtime: &Arc<Runtime>) {
|
||||
match runtime
|
||||
.start(
|
||||
Request {
|
||||
program: self.spec.argv.first().cloned().unwrap_or_default(),
|
||||
argv: self.spec.argv.clone(),
|
||||
cwd: self.spec.cwd.clone(),
|
||||
env: self
|
||||
.spec
|
||||
.env
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
group: None,
|
||||
stdin: Stdio::Pipe,
|
||||
stdout: Stdio::Null,
|
||||
stderr: Stdio::Null,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(child) => {
|
||||
let guard = GroupGuard {
|
||||
runtime: runtime.clone(),
|
||||
group: child.pid,
|
||||
};
|
||||
self.live = Some(Live {
|
||||
child,
|
||||
_guard: guard,
|
||||
});
|
||||
}
|
||||
Err(_) => self.crashed(),
|
||||
}
|
||||
}
|
||||
fn crashed(&mut self) {
|
||||
let now = Instant::now();
|
||||
while self
|
||||
.crashes
|
||||
.front()
|
||||
.is_some_and(|t| now.duration_since(*t) >= Duration::from_secs(10))
|
||||
{
|
||||
self.crashes.pop_front();
|
||||
}
|
||||
self.crashes.push_back(now);
|
||||
self.stopped = self.crashes.len() > 5;
|
||||
}
|
||||
async fn stop(&mut self) {
|
||||
if let Some(mut live) = self.live.take() {
|
||||
drop(live.child.input.take());
|
||||
let _ = tokio::time::timeout(Duration::from_secs(5), async {
|
||||
loop {
|
||||
if live.child.code.borrow().is_some() {
|
||||
break;
|
||||
}
|
||||
if live.child.code.changed().await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
// The guard stops remaining descendants, or the process after its grace period.
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Start the supervisor. Aborting its task stops its service processes.
|
||||
/// The YAML file contains name, argv, optional cwd, and an optional map of environment variables.
|
||||
pub fn supervise(runtime: Arc<Runtime>) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
let mut services: BTreeMap<String, Service> = BTreeMap::new();
|
||||
let mut previous = Vec::new();
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(100));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let content = match runtime
|
||||
.world
|
||||
.root
|
||||
.open("/config/services.yaml", Default::default())
|
||||
.await
|
||||
{
|
||||
Ok(file) => match file.stat().await {
|
||||
Ok(stat) => file.read(0, stat.size as usize).await.ok(),
|
||||
Err(_) => None,
|
||||
},
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some(b"[]".to_vec()),
|
||||
Err(_) => None,
|
||||
};
|
||||
if let Some(content) = content.filter(|c| *c != previous)
|
||||
&& let Ok(specs) = serde_yaml_ng::from_slice::<Vec<Spec>>(&content)
|
||||
{
|
||||
let specs: BTreeMap<_, _> =
|
||||
specs.into_iter().map(|s| (s.name.clone(), s)).collect();
|
||||
let removed: Vec<_> = services
|
||||
.keys()
|
||||
.filter(|name| !specs.contains_key(*name))
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in removed {
|
||||
services.remove(&name).unwrap().stop().await;
|
||||
}
|
||||
for (name, spec) in specs {
|
||||
let service = services.entry(name).or_insert_with(|| Service {
|
||||
spec: spec.clone(),
|
||||
live: None,
|
||||
crashes: VecDeque::new(),
|
||||
stopped: false,
|
||||
});
|
||||
if service.spec != spec {
|
||||
service.stop().await;
|
||||
}
|
||||
service.spec = spec;
|
||||
service.crashes.clear();
|
||||
service.stopped = false;
|
||||
}
|
||||
previous = content;
|
||||
}
|
||||
for service in services.values_mut() {
|
||||
if service
|
||||
.live
|
||||
.as_ref()
|
||||
.is_some_and(|live| live.child.code.borrow().is_some())
|
||||
{
|
||||
service.live = None;
|
||||
service.crashed();
|
||||
}
|
||||
if service.live.is_none() && !service.stopped {
|
||||
service.start(&runtime).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Shares process stdio across stores without a parent-side IO pump.
|
||||
use super::bindings::sausage::process::host::Stdio;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::io::AsyncWrite;
|
||||
use wasmtime_wasi::cli::{AsyncStdinStream, StdinStream};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Io {
|
||||
pub input: Arc<AsyncStdinStream>,
|
||||
pub output: Writer,
|
||||
pub error: Writer,
|
||||
}
|
||||
impl Io {
|
||||
pub fn input(&self) -> sausage_vfs::preview1::Input {
|
||||
Box::new(Pin::from(self.input.async_stream()))
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Writer(Arc<Mutex<Box<dyn AsyncWrite + Send + Sync + Unpin>>>);
|
||||
impl Writer {
|
||||
pub fn new(writer: impl AsyncWrite + Send + Sync + Unpin + 'static) -> Self {
|
||||
Self(Arc::new(Mutex::new(Box::new(writer))))
|
||||
}
|
||||
}
|
||||
impl AsyncWrite for Writer {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bytes: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
Pin::new(&mut **self.0.lock().unwrap()).poll_write(cx, bytes)
|
||||
}
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut **self.0.lock().unwrap()).poll_flush(cx)
|
||||
}
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
// Closing one inherited handle must not close the other handles.
|
||||
self.poll_flush(cx)
|
||||
}
|
||||
}
|
||||
pub(super) fn input(
|
||||
mode: Stdio,
|
||||
parent: Option<&Io>,
|
||||
) -> (Option<tokio::io::DuplexStream>, Arc<AsyncStdinStream>) {
|
||||
match mode {
|
||||
Stdio::Pipe => {
|
||||
let (host, guest) = tokio::io::duplex(8192);
|
||||
(Some(host), Arc::new(AsyncStdinStream::new(guest)))
|
||||
}
|
||||
Stdio::Inherit => (
|
||||
None,
|
||||
parent.expect("inherit requires a parent").input.clone(),
|
||||
),
|
||||
Stdio::Null => (None, Arc::new(AsyncStdinStream::new(tokio::io::empty()))),
|
||||
}
|
||||
}
|
||||
pub(super) fn output(
|
||||
mode: Stdio,
|
||||
parent: Option<&Writer>,
|
||||
) -> (Option<tokio::io::DuplexStream>, Writer) {
|
||||
match mode {
|
||||
Stdio::Pipe => {
|
||||
let (host, guest) = tokio::io::duplex(8192);
|
||||
(Some(host), Writer::new(guest))
|
||||
}
|
||||
Stdio::Inherit => (None, parent.expect("inherit requires a parent").clone()),
|
||||
Stdio::Null => (None, Writer::new(tokio::io::sink())),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user