Left-right single-writer, multi-reader index

This commit is contained in:
Hugh Kaznowski
2026-09-06 16:05:04 +01:00
parent 9fc7540a5b
commit 1c635bc530
9 changed files with 684 additions and 203 deletions
+12 -12
View File
@@ -2,11 +2,10 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::engine::{EngineMessage, IngestedLine, MetricSample};
use crate::engine::{EngineHandle, EngineMessage, IngestedLine, MetricSample};
use crate::timestamp::Timestamp;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::sync::mpsc;
const POLL_INTERVAL: Duration = Duration::from_millis(200);
@@ -14,7 +13,7 @@ const METRICS_POLL_INTERVAL: Duration = Duration::from_secs(15);
/// Reads stdin line by line, sequences and timestamps each line, and forwards it
/// to the engine task.
pub async fn run(engine: mpsc::Sender<EngineMessage>) {
pub async fn run(engine: EngineHandle) {
let stdin = tokio::io::stdin();
let mut lines = BufReader::new(stdin).lines();
let mut sequence: u64 = 0;
@@ -27,7 +26,7 @@ pub async fn run(engine: mpsc::Sender<EngineMessage>) {
};
sequence += 1;
if engine.send(EngineMessage::Ingest(line)).await.is_err() {
if engine.tx.send(EngineMessage::Ingest(line)).await.is_err() {
break;
}
}
@@ -59,7 +58,7 @@ async fn open(path: &Path) -> Option<(File, FileId)> {
/// Tails `path` like `tail -f`, resuming from the start if the file is deleted/recreated or
/// truncated in place rather than exiting or getting stuck; `sequence` keeps incrementing
/// across a recreation since it's ottyl's own line counter, not tied to the source file.
pub async fn run_file(engine: mpsc::Sender<EngineMessage>, path: PathBuf) {
pub async fn run_file(engine: EngineHandle, path: PathBuf) {
let mut sequence: u64 = 0;
'reopen: loop {
@@ -109,7 +108,7 @@ pub async fn run_file(engine: mpsc::Sender<EngineMessage>, path: PathBuf) {
};
sequence += 1;
if engine.send(EngineMessage::Ingest(line)).await.is_err() {
if engine.tx.send(EngineMessage::Ingest(line)).await.is_err() {
return;
}
}
@@ -124,7 +123,7 @@ pub async fn run_file(engine: mpsc::Sender<EngineMessage>, path: PathBuf) {
/// Polls `url` on a fixed interval for a Prometheus/OpenMetrics-format response, pushing every parsed
/// sample straight into the engine. Errors (unreachable endpoint, non-2xx response) are only logged —
/// polling keeps retrying on the next tick rather than exiting.
pub async fn run_metrics_poll(engine: mpsc::Sender<EngineMessage>, url: String) {
pub async fn run_metrics_poll(engine: EngineHandle, url: String) {
let client = reqwest::Client::new();
loop {
match client.get(&url).send().await {
@@ -133,7 +132,7 @@ pub async fn run_metrics_poll(engine: mpsc::Sender<EngineMessage>, url: String)
Ok(body) => {
let now = Timestamp::now();
for sample in parse_openmetrics(&body, now) {
if engine.send(EngineMessage::IngestMetricSample(sample)).await.is_err() {
if engine.tx.send(EngineMessage::IngestMetricSample(sample)).await.is_err() {
return;
}
}
@@ -239,6 +238,7 @@ mod openmetrics_tests {
mod tests {
use super::*;
use std::io::Write;
use crate::engine::test_handle;
use tokio::sync::mpsc;
async fn recv_text(rx: &mut mpsc::Receiver<EngineMessage>) -> String {
@@ -258,7 +258,7 @@ mod tests {
let path = dir.path().join("app.log");
std::fs::write(&path, "first\n").unwrap();
let (tx, mut rx) = mpsc::channel(16);
let (tx, mut rx) = test_handle(16);
tokio::spawn(run_file(tx, path.clone()));
assert_eq!(recv_text(&mut rx).await, "first");
@@ -274,7 +274,7 @@ mod tests {
let path = dir.path().join("app.log");
std::fs::write(&path, "before-restart\n").unwrap();
let (tx, mut rx) = mpsc::channel(16);
let (tx, mut rx) = test_handle(16);
tokio::spawn(run_file(tx, path.clone()));
assert_eq!(recv_text(&mut rx).await, "before-restart");
@@ -291,7 +291,7 @@ mod tests {
let path = dir.path().join("app.log");
std::fs::write(&path, "long line before truncation\n").unwrap();
let (tx, mut rx) = mpsc::channel(16);
let (tx, mut rx) = test_handle(16);
tokio::spawn(run_file(tx, path.clone()));
assert_eq!(recv_text(&mut rx).await, "long line before truncation");
@@ -312,7 +312,7 @@ mod tests {
let path = dir.path().join("app.log");
std::fs::write(&path, "one\n").unwrap();
let (tx, mut rx) = mpsc::channel(16);
let (tx, mut rx) = test_handle(16);
tokio::spawn(run_file(tx, path.clone()));
let first = match tokio::time::timeout(Duration::from_secs(2), rx.recv())