This commit is contained in:
+24
-13
@@ -963,7 +963,8 @@ unsafe extern "C" fn verse_core_connect_v1(
|
|||||||
let features = manifest.features().to_vec();
|
let features = manifest.features().to_vec();
|
||||||
let (command_tx, command_rx) = tokio::sync::mpsc::channel(CONTROL_QUEUE_CAPACITY);
|
let (command_tx, command_rx) = tokio::sync::mpsc::channel(CONTROL_QUEUE_CAPACITY);
|
||||||
let (session_done_tx, session_done_rx) = mpsc::channel();
|
let (session_done_tx, session_done_rx) = mpsc::channel();
|
||||||
let (session_started_tx, session_started_rx) = mpsc::channel();
|
let (session_startup, session_prepared_rx, session_commit_tx, session_outcome_rx) =
|
||||||
|
transport::session_startup();
|
||||||
let (event_tx, event_rx) = bounded_session_events();
|
let (event_tx, event_rx) = bounded_session_events();
|
||||||
let publication = Arc::new(Mutex::new(SessionPublication::Starting));
|
let publication = Arc::new(Mutex::new(SessionPublication::Starting));
|
||||||
let worker_publication = Arc::clone(&publication);
|
let worker_publication = Arc::clone(&publication);
|
||||||
@@ -1019,22 +1020,18 @@ unsafe extern "C" fn verse_core_connect_v1(
|
|||||||
command_rx,
|
command_rx,
|
||||||
event_tx,
|
event_tx,
|
||||||
cancellation,
|
cancellation,
|
||||||
Some(session_started_tx),
|
Some(session_startup),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
event_tx.close();
|
event_tx.close();
|
||||||
|
runtime.block_on(async move {
|
||||||
|
let (result_tx, mut result_rx) = tokio::sync::mpsc::channel(1);
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
if test_session_failure {
|
if test_session_failure {
|
||||||
let mut publication = lock(&worker_publication);
|
let _ = result_tx.try_send(Err(CoreError::Transport));
|
||||||
*publication = SessionPublication::Failed(CoreError::Transport);
|
|
||||||
let _ = session_started_tx.send(());
|
|
||||||
drop(publication);
|
|
||||||
let _ = bridge.join();
|
|
||||||
let _ = session_done_tx.send(());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let _ = session_started_tx.send(());
|
let _result_tx = result_tx;
|
||||||
runtime.block_on(async move {
|
transport::await_session_startup(&mut result_rx, session_startup).await?;
|
||||||
let mut command_rx = command_rx;
|
let mut command_rx = command_rx;
|
||||||
while !cancellation.is_cancelled() {
|
while !cancellation.is_cancelled() {
|
||||||
if matches!(
|
if matches!(
|
||||||
@@ -1045,8 +1042,8 @@ unsafe extern "C" fn verse_core_connect_v1(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
Err(CoreError::Cancelled)
|
Err(CoreError::Cancelled)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
let _ = bridge.join();
|
let _ = bridge.join();
|
||||||
if let Err(error) = session_result {
|
if let Err(error) = session_result {
|
||||||
@@ -1084,13 +1081,27 @@ unsafe extern "C" fn verse_core_connect_v1(
|
|||||||
};
|
};
|
||||||
*lock(&handle.session_done) = Some(session_done_rx);
|
*lock(&handle.session_done) = Some(session_done_rx);
|
||||||
*lock(&handle.session_worker) = Some(session_worker);
|
*lock(&handle.session_worker) = Some(session_worker);
|
||||||
if session_started_rx
|
if session_prepared_rx
|
||||||
.recv_timeout(Duration::from_secs(10))
|
.recv_timeout(Duration::from_secs(10))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
cancel_inner(&handle);
|
cancel_inner(&handle);
|
||||||
return INTERNAL;
|
return INTERNAL;
|
||||||
}
|
}
|
||||||
|
let _ = session_commit_tx.send(());
|
||||||
|
let startup_result = match session_outcome_rx.recv_timeout(Duration::from_secs(10)) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
cancel_inner(&handle);
|
||||||
|
return INTERNAL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(error) = startup_result {
|
||||||
|
let mut publication = lock(&publication);
|
||||||
|
if *publication == SessionPublication::Starting {
|
||||||
|
*publication = SessionPublication::Failed(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let mut publication = lock(&publication);
|
let mut publication = lock(&publication);
|
||||||
let mut state = lock(&handle.state);
|
let mut state = lock(&handle.state);
|
||||||
|
|||||||
+120
-5
@@ -167,6 +167,57 @@ pub struct TransportSession {
|
|||||||
features: Vec<String>,
|
features: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SessionStartup {
|
||||||
|
prepared: std::sync::mpsc::Sender<()>,
|
||||||
|
commit: tokio::sync::oneshot::Receiver<()>,
|
||||||
|
outcome: std::sync::mpsc::Sender<Result<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn session_startup() -> (
|
||||||
|
SessionStartup,
|
||||||
|
std::sync::mpsc::Receiver<()>,
|
||||||
|
tokio::sync::oneshot::Sender<()>,
|
||||||
|
std::sync::mpsc::Receiver<Result<()>>,
|
||||||
|
) {
|
||||||
|
let (prepared_tx, prepared_rx) = std::sync::mpsc::channel();
|
||||||
|
let (commit_tx, commit_rx) = tokio::sync::oneshot::channel();
|
||||||
|
let (outcome_tx, outcome_rx) = std::sync::mpsc::channel();
|
||||||
|
(
|
||||||
|
SessionStartup {
|
||||||
|
prepared: prepared_tx,
|
||||||
|
commit: commit_rx,
|
||||||
|
outcome: outcome_tx,
|
||||||
|
},
|
||||||
|
prepared_rx,
|
||||||
|
commit_tx,
|
||||||
|
outcome_rx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn await_session_startup(
|
||||||
|
result_rx: &mut tokio::sync::mpsc::Receiver<Result<()>>,
|
||||||
|
startup: SessionStartup,
|
||||||
|
) -> Result<()> {
|
||||||
|
startup
|
||||||
|
.prepared
|
||||||
|
.send(())
|
||||||
|
.map_err(|_| CoreError::Cancelled)?;
|
||||||
|
let mut loop_result = std::pin::pin!(result_rx.recv());
|
||||||
|
let mut commit = std::pin::pin!(startup.commit);
|
||||||
|
let result = std::future::poll_fn(|context| {
|
||||||
|
if let std::task::Poll::Ready(result) =
|
||||||
|
std::future::Future::poll(loop_result.as_mut(), context)
|
||||||
|
{
|
||||||
|
return std::task::Poll::Ready(result.unwrap_or(Err(CoreError::Transport)));
|
||||||
|
}
|
||||||
|
std::future::Future::poll(commit.as_mut(), context)
|
||||||
|
.map(|result| result.map_err(|_| CoreError::Cancelled))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let _ = startup.outcome.send(result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum SessionCommand {
|
pub enum SessionCommand {
|
||||||
Input(Vec<u8>),
|
Input(Vec<u8>),
|
||||||
@@ -341,7 +392,7 @@ impl TransportSession {
|
|||||||
commands: tokio::sync::mpsc::Receiver<SessionCommand>,
|
commands: tokio::sync::mpsc::Receiver<SessionCommand>,
|
||||||
events: SessionEventSender,
|
events: SessionEventSender,
|
||||||
cancellation: Cancellation,
|
cancellation: Cancellation,
|
||||||
started: Option<std::sync::mpsc::Sender<()>>,
|
startup: Option<SessionStartup>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Self {
|
let Self {
|
||||||
endpoint,
|
endpoint,
|
||||||
@@ -369,8 +420,16 @@ impl TransportSession {
|
|||||||
result_tx.clone(),
|
result_tx.clone(),
|
||||||
));
|
));
|
||||||
let datagrams = tokio::spawn(datagram_loop(connection.clone(), events.clone(), result_tx));
|
let datagrams = tokio::spawn(datagram_loop(connection.clone(), events.clone(), result_tx));
|
||||||
if let Some(started) = started {
|
if let Some(startup) = startup {
|
||||||
let _ = started.send(());
|
if let Err(error) = await_session_startup(&mut result_rx, startup).await {
|
||||||
|
connection.close(APPLICATION_ERROR, b"session stopped");
|
||||||
|
writer.abort();
|
||||||
|
reliable.abort();
|
||||||
|
datagrams.abort();
|
||||||
|
endpoint.wait_idle().await;
|
||||||
|
events.close();
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
if cancellation.check().is_err() {
|
if cancellation.check().is_err() {
|
||||||
@@ -968,8 +1027,9 @@ mod tests {
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
bounded_lookup, bounded_session_events, decode_reliable_frame, encode_reliable_frame,
|
await_session_startup, bounded_lookup, bounded_session_events, decode_reliable_frame,
|
||||||
hello_length, Cancellation, SessionEvent, HELLO_LIMIT,
|
encode_reliable_frame, hello_length, session_startup, Cancellation, SessionEvent,
|
||||||
|
HELLO_LIMIT,
|
||||||
};
|
};
|
||||||
use crate::error::CoreError;
|
use crate::error::CoreError;
|
||||||
use crate::media::{EncodedUnit, MediaChannel};
|
use crate::media::{EncodedUnit, MediaChannel};
|
||||||
@@ -1096,4 +1156,59 @@ mod tests {
|
|||||||
Err(tokio::sync::mpsc::error::TrySendError::Full(_))
|
Err(tokio::sync::mpsc::error::TrySendError::Full(_))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn queued_loop_failure_wins_before_session_ready() {
|
||||||
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("runtime");
|
||||||
|
let (result_tx, mut result_rx) = tokio::sync::mpsc::channel(1);
|
||||||
|
result_tx
|
||||||
|
.try_send(Err(crate::error::CoreError::Protocol))
|
||||||
|
.expect("queue terminal loop result");
|
||||||
|
let (startup, prepared, commit, outcome) = session_startup();
|
||||||
|
let running =
|
||||||
|
runtime.spawn(async move { await_session_startup(&mut result_rx, startup).await });
|
||||||
|
runtime.block_on(async { tokio::task::yield_now().await });
|
||||||
|
prepared.recv().expect("session prepared");
|
||||||
|
let _ = commit.send(());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
outcome.recv().expect("startup outcome"),
|
||||||
|
Err(crate::error::CoreError::Protocol)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
runtime.block_on(running).expect("startup task"),
|
||||||
|
Err(crate::error::CoreError::Protocol)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loop_failure_after_session_ready_remains_for_publication_race() {
|
||||||
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("runtime");
|
||||||
|
let (result_tx, mut result_rx) = tokio::sync::mpsc::channel(1);
|
||||||
|
let (startup, prepared, commit, outcome) = session_startup();
|
||||||
|
let running = runtime.spawn(async move {
|
||||||
|
let result = await_session_startup(&mut result_rx, startup).await;
|
||||||
|
(result, result_rx)
|
||||||
|
});
|
||||||
|
runtime.block_on(async { tokio::task::yield_now().await });
|
||||||
|
prepared.recv().expect("session prepared");
|
||||||
|
commit.send(()).expect("commit startup");
|
||||||
|
|
||||||
|
let (startup_result, mut result_rx) = runtime.block_on(running).expect("startup task");
|
||||||
|
assert_eq!(startup_result, Ok(()));
|
||||||
|
assert_eq!(outcome.recv().expect("startup outcome"), Ok(()));
|
||||||
|
result_tx
|
||||||
|
.try_send(Err(CoreError::Protocol))
|
||||||
|
.expect("queue post-ready terminal result");
|
||||||
|
assert_eq!(
|
||||||
|
runtime.block_on(result_rx.recv()),
|
||||||
|
Some(Err(CoreError::Protocol))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+142
-21
@@ -209,6 +209,39 @@ pub(crate) struct Oracle {
|
|||||||
pub(crate) ready: Ready,
|
pub(crate) ready: Ready,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OracleStartup {
|
||||||
|
child: Option<Child>,
|
||||||
|
directory: Option<std::path::PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OracleStartup {
|
||||||
|
fn new(directory: std::path::PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
child: None,
|
||||||
|
directory: Some(directory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(mut self, ready: Ready) -> Oracle {
|
||||||
|
Oracle {
|
||||||
|
child: self.child.take().expect("started oracle child"),
|
||||||
|
directory: self.directory.take().expect("oracle directory"),
|
||||||
|
ready,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for OracleStartup {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(child) = self.child.as_mut() {
|
||||||
|
stop_child(child, self.directory.as_deref());
|
||||||
|
}
|
||||||
|
if let Some(directory) = self.directory.as_ref() {
|
||||||
|
let _ = fs::remove_dir_all(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn production_oracles_can_run_concurrently_with_isolated_state() {
|
fn production_oracles_can_run_concurrently_with_isolated_state() {
|
||||||
let (first_ready_tx, first_ready_rx) = std::sync::mpsc::channel();
|
let (first_ready_tx, first_ready_rx) = std::sync::mpsc::channel();
|
||||||
@@ -235,10 +268,61 @@ fn production_oracles_can_run_concurrently_with_isolated_state() {
|
|||||||
assert!(concurrent, "second isolated oracle was globally serialized");
|
assert!(concurrent, "second isolated oracle was globally serialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oracle_startup_timeout_kills_child_and_removes_directory() {
|
||||||
|
let directory = Oracle::unique_directory();
|
||||||
|
let mut command = Command::new("sh");
|
||||||
|
command.args(["-c", "while :; do sleep 1; done"]);
|
||||||
|
let started = Instant::now();
|
||||||
|
|
||||||
|
let result = Oracle::start_in("", directory.clone(), command, Duration::from_millis(20));
|
||||||
|
|
||||||
|
assert!(result.is_err(), "non-ready child unexpectedly became ready");
|
||||||
|
assert!(started.elapsed() < Duration::from_secs(2));
|
||||||
|
assert!(!directory.exists(), "timed-out oracle directory leaked");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oracle_source_failure_removes_directory_before_spawn() {
|
||||||
|
let directory = Oracle::unique_directory();
|
||||||
|
fs::create_dir(directory.join("main.go")).expect("block source file creation");
|
||||||
|
|
||||||
|
let result = Oracle::start_in(
|
||||||
|
"",
|
||||||
|
directory.clone(),
|
||||||
|
Command::new("go"),
|
||||||
|
Duration::from_millis(20),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_err(), "invalid source path unexpectedly started");
|
||||||
|
assert!(!directory.exists(), "failed oracle directory leaked");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oracle_spawn_failure_removes_directory() {
|
||||||
|
let directory = Oracle::unique_directory();
|
||||||
|
|
||||||
|
let result = Oracle::start_in(
|
||||||
|
"",
|
||||||
|
directory.clone(),
|
||||||
|
Command::new("/definitely/missing/versevdi-go"),
|
||||||
|
Duration::from_millis(20),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_err(), "missing executable unexpectedly started");
|
||||||
|
assert!(!directory.exists(), "spawn-failed oracle directory leaked");
|
||||||
|
}
|
||||||
|
|
||||||
impl Oracle {
|
impl Oracle {
|
||||||
pub(crate) fn start(mode: &str) -> Self {
|
pub(crate) fn start(mode: &str) -> Self {
|
||||||
|
let directory = Self::unique_directory();
|
||||||
|
Self::start_in(mode, directory, Command::new("go"), Duration::from_secs(20))
|
||||||
|
.unwrap_or_else(|error| panic!("{error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_directory() -> std::path::PathBuf {
|
||||||
static NEXT: AtomicUsize = AtomicUsize::new(1);
|
static NEXT: AtomicUsize = AtomicUsize::new(1);
|
||||||
let directory = loop {
|
loop {
|
||||||
let candidate = std::env::temp_dir().join(format!(
|
let candidate = std::env::temp_dir().join(format!(
|
||||||
"versevdi-rust-gateway-oracle-{}-{}",
|
"versevdi-rust-gateway-oracle-{}-{}",
|
||||||
std::process::id(),
|
std::process::id(),
|
||||||
@@ -249,13 +333,26 @@ impl Oracle {
|
|||||||
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
|
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||||
Err(error) => panic!("create oracle directory: {error}"),
|
Err(error) => panic!("create oracle directory: {error}"),
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_in(
|
||||||
|
mode: &str,
|
||||||
|
directory: std::path::PathBuf,
|
||||||
|
mut command: Command,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let mut startup = OracleStartup::new(directory.clone());
|
||||||
let source = directory.join("main.go");
|
let source = directory.join("main.go");
|
||||||
let ready_path = directory.join("ready.json");
|
let ready_path = directory.join("ready.json");
|
||||||
let stop_path = directory.join("stop");
|
let stop_path = directory.join("stop");
|
||||||
fs::write(&source, ORACLE_SOURCE).expect("write oracle source");
|
fs::write(&source, ORACLE_SOURCE)
|
||||||
let child = Command::new("go")
|
.map_err(|error| format!("write Go oracle source: {error}"))?;
|
||||||
.args(["run", source.to_str().expect("source path is UTF-8")])
|
let source = source
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| "Go oracle source path is not UTF-8".to_owned())?;
|
||||||
|
let child = command
|
||||||
|
.args(["run", source])
|
||||||
.arg(&ready_path)
|
.arg(&ready_path)
|
||||||
.arg(&stop_path)
|
.arg(&stop_path)
|
||||||
.env("VERSEVDI_RUST_ORACLE_MODE", mode)
|
.env("VERSEVDI_RUST_ORACLE_MODE", mode)
|
||||||
@@ -263,27 +360,29 @@ impl Oracle {
|
|||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("start Go gateway oracle");
|
.map_err(|error| format!("start Go gateway oracle: {error}"))?;
|
||||||
let deadline = Instant::now() + Duration::from_secs(20);
|
startup.child = Some(child);
|
||||||
|
let deadline = Instant::now() + timeout;
|
||||||
while Instant::now() < deadline {
|
while Instant::now() < deadline {
|
||||||
if let Ok(bytes) = fs::read(&ready_path) {
|
if let Ok(bytes) = fs::read(&ready_path) {
|
||||||
if let Ok(ready) = serde_json::from_slice(&bytes) {
|
if let Ok(ready) = serde_json::from_slice(&bytes) {
|
||||||
return Self {
|
return Ok(startup.finish(ready));
|
||||||
child,
|
|
||||||
directory,
|
|
||||||
ready,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(status) = startup
|
||||||
|
.child
|
||||||
|
.as_mut()
|
||||||
|
.expect("started oracle child")
|
||||||
|
.try_wait()
|
||||||
|
.map_err(|error| format!("poll Go gateway oracle: {error}"))?
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"Go gateway oracle exited before becoming ready: status={status}"
|
||||||
|
));
|
||||||
|
}
|
||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
let output = child.wait_with_output().expect("collect Go oracle output");
|
Err("Go gateway oracle did not become ready before the startup deadline".to_owned())
|
||||||
panic!(
|
|
||||||
"Go gateway oracle did not become ready: status={} stdout={} stderr={}",
|
|
||||||
output.status,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider_starts(&self) -> usize {
|
fn provider_starts(&self) -> usize {
|
||||||
@@ -317,12 +416,34 @@ impl Oracle {
|
|||||||
|
|
||||||
impl Drop for Oracle {
|
impl Drop for Oracle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let _ = fs::write(self.directory.join("stop"), []);
|
stop_child(&mut self.child, Some(&self.directory));
|
||||||
let _ = self.child.wait();
|
|
||||||
let _ = fs::remove_dir_all(&self.directory);
|
let _ = fs::remove_dir_all(&self.directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stop_child(child: &mut Child, directory: Option<&std::path::Path>) {
|
||||||
|
if let Some(directory) = directory {
|
||||||
|
let _ = fs::write(directory.join("stop"), []);
|
||||||
|
}
|
||||||
|
if wait_for_child(child, Duration::from_millis(250)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if child.kill().is_ok() {
|
||||||
|
let _ = wait_for_child(child, Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_child(child: &mut Child, timeout: Duration) -> bool {
|
||||||
|
let deadline = Instant::now() + timeout;
|
||||||
|
loop {
|
||||||
|
match child.try_wait() {
|
||||||
|
Ok(Some(_)) => return true,
|
||||||
|
Ok(None) if Instant::now() < deadline => thread::sleep(Duration::from_millis(5)),
|
||||||
|
Ok(None) | Err(_) => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn test_key(encoded: &str) -> Arc<dyn SigningKey> {
|
pub(crate) fn test_key(encoded: &str) -> Arc<dyn SigningKey> {
|
||||||
let der = STANDARD.decode(encoded).expect("decode test key");
|
let der = STANDARD.decode(encoded).expect("decode test key");
|
||||||
rustls::crypto::ring::default_provider()
|
rustls::crypto::ring::default_provider()
|
||||||
|
|||||||
Reference in New Issue
Block a user