Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

even nicer shutdown, probably maybe

Changed files
+78 -26
jetstream
src
spacedust
ufos
-1
jetstream/src/error.rs
···
pub enum JetstreamEventError {
#[error("failed to load built-in zstd dictionary for decoding: {0}")]
CompressionDictionaryError(io::Error),
-
#[error("failed to send ping or pong: {0}")]
PingPongError(#[from] tokio_tungstenite::tungstenite::Error),
#[error("jetstream event receiver closed")]
+9 -4
spacedust/src/consumer.rs
···
loop {
if shutdown.is_cancelled() {
log::info!("exiting consumer for shutdown");
-
break;
+
return Ok(());
}
let Some(event) = receiver.recv().await else {
-
log::error!("could not receive jetstream event, shutting down...");
-
shutdown.cancel();
+
log::error!("could not receive jetstream event, bailing");
break;
};
···
continue;
};
-
let jv = record.get().parse()?;
+
let jv = match record.get().parse() {
+
Ok(v) => v,
+
Err(e) => {
+
log::warn!("jetstream record failed to parse, ignoring: {e}");
+
continue;
+
}
+
};
// todo: indicate if the link limit was reached (-> links omitted)
for (i, link) in collect_links(&jv).into_iter().enumerate() {
+22 -2
spacedust/src/error.rs
···
use thiserror::Error;
#[derive(Debug, Error)]
+
pub enum MainTaskError {
+
#[error(transparent)]
+
ConsumerTaskError(#[from] ConsumerError),
+
#[error(transparent)]
+
ServerTaskError(#[from] ServerError),
+
}
+
+
#[derive(Debug, Error)]
pub enum ConsumerError {
#[error(transparent)]
JetstreamConnectionError(#[from] jetstream::error::ConnectionError),
#[error(transparent)]
JetstreamConfigValidationError(#[from] jetstream::error::ConfigValidationError),
-
#[error(transparent)]
-
JsonParseError(#[from] tinyjson::JsonParseError),
#[error("jetstream ended")]
JetstreamEnded
}
+
+
#[derive(Debug, Error)]
+
pub enum ServerError {
+
#[error("failed to configure server logger: {0}")]
+
ConfigLogError(std::io::Error),
+
#[error("failed to render json for openapi: {0}")]
+
OpenApiJsonFail(serde_json::Error),
+
#[error(transparent)]
+
FailedToBuildServer(#[from] dropshot::BuildError),
+
#[error("server exited: {0}")]
+
ServerExited(String),
+
#[error("server closed badly: {0}")]
+
BadClose(String),
+
}
+35 -7
spacedust/src/main.rs
···
+
use spacedust::error::MainTaskError;
use spacedust::consumer;
use spacedust::server;
···
log::error!("failed to install metrics server: {e:?}");
};
+
let mut tasks: tokio::task::JoinSet<Result<(), MainTaskError>> = tokio::task::JoinSet::new();
+
let server_shutdown = shutdown.clone();
-
let serving = tokio::spawn(async move {
-
server::serve(b, server_shutdown).await
+
tasks.spawn(async move {
+
server::serve(b, server_shutdown).await?;
+
Ok(())
});
let consumer_shutdown = shutdown.clone();
-
let consuming = tokio::spawn(async move {
-
consumer::consume(consumer_sender, args.jetstream, None, args.jetstream_no_zstd, consumer_shutdown).await
+
tasks.spawn(async move {
+
consumer::consume(
+
consumer_sender,
+
args.jetstream,
+
None,
+
args.jetstream_no_zstd,
+
consumer_shutdown
+
)
+
.await?;
+
Ok(())
});
-
let (served, consumed) = tokio::join!(serving, consuming);
-
log::info!("serving ended: {served:?}");
-
log::info!("consuming ended: {consumed:?}");
+
tokio::select! {
+
_ = shutdown.cancelled() => log::warn!("shutdown requested"),
+
Some(r) = tasks.join_next() => {
+
log::warn!("a task exited, shutting down: {r:?}");
+
shutdown.cancel();
+
}
+
}
+
+
tokio::select! {
+
_ = async {
+
while let Some(completed) = tasks.join_next().await {
+
log::info!("shutdown: task completed: {completed:?}");
+
}
+
} => {},
+
_ = tokio::time::sleep(std::time::Duration::from_secs(3)) => {
+
log::info!("shutdown: not all tasks completed on time. aborting...");
+
tasks.shutdown().await;
+
},
+
}
log::info!("bye!");
+11 -11
spacedust/src/server.rs
···
+
use crate::error::ServerError;
use crate::subscriber::Subscriber;
use metrics::{histogram, counter};
use std::sync::Arc;
···
const INDEX_HTML: &str = include_str!("../static/index.html");
const FAVICON: &[u8] = include_bytes!("../static/favicon.ico");
-
pub async fn serve(b: broadcast::Sender<LinkEvent>, shutdown: CancellationToken) -> Result<(), String> {
+
pub async fn serve(b: broadcast::Sender<LinkEvent>, shutdown: CancellationToken) -> Result<(), ServerError> {
let config_logging = ConfigLogging::StderrTerminal {
level: ConfigLoggingLevel::Info,
};
let log = config_logging
.to_logger("example-basic")
-
.map_err(|error| format!("failed to create logger: {}", error))?;
+
.map_err(ServerError::ConfigLogError)?;
let mut api = ApiDescription::new();
api.register(index).unwrap();
···
.contact_name("part of @microcosm.blue")
.contact_url("https://microcosm.blue")
.json()
-
.map_err(|e| e.to_string())?,
+
.map_err(ServerError::OpenApiJsonFail)?,
);
let sub_shutdown = shutdown.clone();
···
bind_address: "0.0.0.0:9998".parse().unwrap(),
..Default::default()
})
-
.start()
-
.map_err(|error| format!("failed to create server: {}", error))?;
+
.start()?;
tokio::select! {
s = server.wait_for_shutdown() => {
-
log::error!("dropshot server ended: {s:?}");
-
s
+
s.map_err(ServerError::ServerExited)?;
+
log::info!("server shut down normally.");
},
_ = shutdown.cancelled() => {
-
log::info!("shutting down server");
-
server.close().await?;
-
Err("shutdown requested".to_string())
-
}
+
log::info!("shutting down: closing server");
+
server.close().await.map_err(ServerError::BadClose)?;
+
},
}
+
Ok(())
}
#[derive(Debug, Clone)]
+1 -1
ufos/src/storage_fjall.rs
···
///
/// new data format, roughly:
///
-
/// Partion: 'global'
+
/// Partition: 'global'
///
/// - Global sequence counter (is the jetstream cursor -- monotonic with many gaps)
/// - key: "js_cursor" (literal)