sausage core: modules per topic, journal writer thread with WAL and batches, hash cache by metadata, 49 tests per rule
Blank stdout lines give no warning. core.exit requests the exit in its own handler.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
//! Process metadata, process lifetime, and attached children.
|
||||
use crate::{Host, RpcResult};
|
||||
use serde_json::{Value, json};
|
||||
use tokio::sync::{mpsc, oneshot, watch};
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
/// A snapshot of process metadata for inspection and the VFS.
|
||||
pub struct ProcessInfo {
|
||||
/// The plugin that supplied the process code.
|
||||
pub plugin: String,
|
||||
/// The SHA-256 of its component, or native for a native plugin.
|
||||
pub version: String,
|
||||
/// The method without its plugin prefix.
|
||||
pub method: String,
|
||||
/// The object supplied by the caller.
|
||||
pub params: Value,
|
||||
/// The attached parent, or no parent for a detached process.
|
||||
pub parent: Option<String>,
|
||||
/// Whether this process survives the end of its caller.
|
||||
pub detached: bool,
|
||||
/// The start time in milliseconds since the Unix epoch.
|
||||
pub start_time: u128,
|
||||
/// The current lifecycle state.
|
||||
pub state: String,
|
||||
}
|
||||
pub(crate) struct Process {
|
||||
pub(crate) info: ProcessInfo,
|
||||
pub(crate) input: Option<mpsc::UnboundedSender<Value>>,
|
||||
pub(crate) abort: Option<tokio::task::AbortHandle>,
|
||||
pub(crate) caller: String,
|
||||
pub(crate) id: Option<Value>,
|
||||
pub(crate) answer: Option<oneshot::Sender<RpcResult>>,
|
||||
pub(crate) ended: watch::Sender<bool>,
|
||||
}
|
||||
pub(crate) struct ProcessGuard {
|
||||
pub(crate) host: Host,
|
||||
pub(crate) pid: String,
|
||||
}
|
||||
impl Drop for ProcessGuard {
|
||||
fn drop(&mut self) {
|
||||
self.host.finish(&self.pid, "process task ended");
|
||||
}
|
||||
}
|
||||
|
||||
impl Host {
|
||||
/// Remove a process and release its locks before asynchronous journal completion.
|
||||
pub(crate) fn finish(&self, pid: &str, status: &str) {
|
||||
let mut state = self.0.state.lock().unwrap();
|
||||
let process = state.processes.remove(pid);
|
||||
if let Some(p) = process {
|
||||
self.0.journal.write(pid, &p.info.plugin, "core", status);
|
||||
let logs = self.0.journal.query_later(json!({"pid":pid,"limit":20}));
|
||||
Self::release_locks(&mut state, pid);
|
||||
let status = status.to_string();
|
||||
tokio::spawn(async move {
|
||||
let lines = logs.await.expect("journal writer stopped");
|
||||
if let Some(answer) = p.answer {
|
||||
let failure = json!({
|
||||
"code": -32002,
|
||||
"message": "process ended without an answer",
|
||||
"data": { "status": status, "lines": lines }
|
||||
});
|
||||
let _ = answer.send(Err(failure));
|
||||
}
|
||||
p.ended.send_replace(true);
|
||||
});
|
||||
let children: Vec<_> = state
|
||||
.processes
|
||||
.iter()
|
||||
.filter(|(_, p)| p.info.parent.as_deref() == Some(pid))
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
drop(state);
|
||||
for child in children {
|
||||
self.begin_stop(&child);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Close stdin and start the same five-second deadline for each attached child.
|
||||
pub(crate) fn begin_stop(&self, pid: &str) -> Option<watch::Receiver<bool>> {
|
||||
let mut state = self.0.state.lock().unwrap();
|
||||
let p = state.processes.get_mut(pid)?;
|
||||
let ended = p.ended.subscribe();
|
||||
if p.info.state == "stopping" {
|
||||
return Some(ended);
|
||||
}
|
||||
p.info.state = "stopping".into();
|
||||
p.input.take();
|
||||
self.0.journal.write(pid, &p.info.plugin, "core", "stop");
|
||||
let children: Vec<_> = state
|
||||
.processes
|
||||
.iter()
|
||||
.filter(|(_, p)| p.info.parent.as_deref() == Some(pid))
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
drop(state);
|
||||
for child in children {
|
||||
self.begin_stop(&child);
|
||||
}
|
||||
let h = self.clone();
|
||||
let pid = pid.to_string();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
h.kill(&pid);
|
||||
});
|
||||
Some(ended)
|
||||
}
|
||||
/// Abort a process task and perform its ended-state cleanup.
|
||||
pub(crate) fn kill(&self, pid: &str) {
|
||||
if let Some(p) = self.0.state.lock().unwrap().processes.get(pid) {
|
||||
self.0.journal.write(pid, &p.info.plugin, "core", "kill");
|
||||
if let Some(a) = &p.abort {
|
||||
a.abort();
|
||||
}
|
||||
}
|
||||
self.finish(pid, "killed");
|
||||
}
|
||||
/// Stop every process in parallel and wait for journal writes to finish.
|
||||
pub async fn stop(&self) {
|
||||
let ids: Vec<_> = self.processes().into_keys().collect();
|
||||
let mut waits = Vec::new();
|
||||
for pid in ids {
|
||||
if let Some(n) = self.begin_stop(&pid) {
|
||||
waits.push(n);
|
||||
}
|
||||
}
|
||||
for mut n in waits {
|
||||
let _ = n.wait_for(|ended| *ended).await;
|
||||
}
|
||||
self.0.journal.query(json!({"limit":0})).await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user