-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a non-blocking child process interface (#211)
- Loading branch information
Showing
22 changed files
with
414 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
use bstr::BString; | ||
use bytes::BytesMut; | ||
use mlua::prelude::*; | ||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; | ||
|
||
const CHUNK_SIZE: usize = 8; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ChildProcessReader<R: AsyncRead>(pub R); | ||
#[derive(Debug, Clone)] | ||
pub struct ChildProcessWriter<W: AsyncWrite>(pub W); | ||
|
||
impl<R: AsyncRead + Unpin> ChildProcessReader<R> { | ||
pub async fn read(&mut self, chunk_size: Option<usize>) -> LuaResult<Vec<u8>> { | ||
let mut buf = BytesMut::with_capacity(chunk_size.unwrap_or(CHUNK_SIZE)); | ||
self.0.read_buf(&mut buf).await?; | ||
|
||
Ok(buf.to_vec()) | ||
} | ||
|
||
pub async fn read_to_end(&mut self) -> LuaResult<Vec<u8>> { | ||
let mut buf = vec![]; | ||
self.0.read_to_end(&mut buf).await?; | ||
|
||
Ok(buf) | ||
} | ||
} | ||
|
||
impl<R: AsyncRead + Unpin + 'static> LuaUserData for ChildProcessReader<R> { | ||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { | ||
methods.add_async_method_mut("read", |lua, this, chunk_size: Option<usize>| async move { | ||
let buf = this.read(chunk_size).await?; | ||
|
||
if buf.is_empty() { | ||
return Ok(LuaValue::Nil); | ||
} | ||
|
||
Ok(LuaValue::String(lua.create_string(buf)?)) | ||
}); | ||
|
||
methods.add_async_method_mut("readToEnd", |lua, this, ()| async { | ||
Ok(lua.create_string(this.read_to_end().await?)) | ||
}); | ||
} | ||
} | ||
|
||
impl<W: AsyncWrite + Unpin> ChildProcessWriter<W> { | ||
pub async fn write(&mut self, data: BString) -> LuaResult<()> { | ||
self.0.write_all(data.as_ref()).await?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<W: AsyncWrite + Unpin + 'static> LuaUserData for ChildProcessWriter<W> { | ||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { | ||
methods.add_async_method_mut("write", |_, this, data| async { this.write(data).await }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
local process = require("@lune/process") | ||
|
||
-- Killing a child process should work as expected | ||
|
||
local message = "Hello, world!" | ||
local child = process.create("cat") | ||
|
||
child.stdin:write(message) | ||
child.kill() | ||
|
||
assert(child.status().code == 9, "Child process should have an exit code of 9 (SIGKILL)") | ||
|
||
assert( | ||
child.stdout:readToEnd() == message, | ||
"Reading from stdout of child process should work even after kill" | ||
) | ||
|
||
local stdinWriteOk = pcall(function() | ||
child.stdin:write(message) | ||
end) | ||
assert(not stdinWriteOk, "Writing to stdin of child process should not work after kill") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
local process = require("@lune/process") | ||
|
||
-- Spawning a child process should not block the thread | ||
|
||
local childThread = coroutine.create(process.create) | ||
|
||
local ok, err = coroutine.resume(childThread, "echo", { "hello, world" }) | ||
assert(ok, err) | ||
|
||
assert( | ||
coroutine.status(childThread) == "dead", | ||
"Child process should not block the thread it is running on" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
local process = require("@lune/process") | ||
|
||
-- The exit code of an child process should be correct | ||
|
||
local randomExitCode = math.random(0, 255) | ||
local isOk = randomExitCode == 0 | ||
local child = process.create("exit", { tostring(randomExitCode) }, { shell = true }) | ||
local status = child.status() | ||
|
||
assert( | ||
status.code == randomExitCode, | ||
`Child process exited with wrong exit code, expected {randomExitCode}` | ||
) | ||
|
||
assert(status.ok == isOk, `Child status should be {if status.ok then "ok" else "not ok"}`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
local process = require("@lune/process") | ||
|
||
-- Should be able to write and read from child process streams | ||
|
||
local msg = "hello, world" | ||
|
||
local catChild = process.create("cat") | ||
catChild.stdin:write(msg) | ||
assert( | ||
msg == catChild.stdout:read(#msg), | ||
"Failed to write to stdin or read from stdout of child process" | ||
) | ||
|
||
local echoChild = if process.os == "windows" | ||
then process.create("/c", { "echo", msg, "1>&2" }, { shell = "cmd" }) | ||
else process.create("echo", { msg, ">>/dev/stderr" }, { shell = true }) | ||
|
||
assert(msg == echoChild.stderr:read(#msg), "Failed to read from stderr of child process") |
Oops, something went wrong.