Initialize all the tasks in the main function

This commit is contained in:
Riley Apeldoorn 2022-06-18 22:50:29 +02:00
parent c6bdbfbc52
commit 7d392ed3fa
6 changed files with 205 additions and 41 deletions

2
Cargo.lock generated
View file

@ -485,6 +485,7 @@ dependencies = [
"serde_json", "serde_json",
"sqlx", "sqlx",
"tokio", "tokio",
"tokio-stream",
"url", "url",
] ]
@ -1429,6 +1430,7 @@ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-util",
] ]
[[package]] [[package]]

View file

@ -15,3 +15,4 @@ axum = { version = '*', features = [ "ws", "serde_json" ] }
url = { version = '*', features = [ "serde" ] } url = { version = '*', features = [ "serde" ] }
sqlx = { version = '*', features = [ "postgres", "runtime-tokio-native-tls" ] } sqlx = { version = '*', features = [ "postgres", "runtime-tokio-native-tls" ] }
openssl = '*' openssl = '*'
tokio-stream = { version = '*', features = [ "sync" ] }

View file

@ -85,11 +85,12 @@ impl Activity {
Ok (()) Ok (())
} }
// Get all delivery targets as urls. /// Get all delivery targets as urls.
async fn delivery_targets (&self) -> Result<Vec<reqwest::Url>> { async fn delivery_targets (&self) -> Result<Vec<reqwest::Url>> {
todo!() todo!()
} }
/// Perform the activity.
pub async fn perform <S> (self, ctx: &mut Context<S>) -> Result<()> where S: sign::Sign { pub async fn perform <S> (self, ctx: &mut Context<S>) -> Result<()> where S: sign::Sign {
todo!() todo!()
} }

View file

@ -93,9 +93,19 @@ mod ctx {
use crate::{ conf::Config, db, Result, sign::Sign, ap, Activity }; use crate::{ conf::Config, db, Result, sign::Sign, ap, Activity };
/// The context of a thread/task.
///
/// The intended usage pattern is to create a single [`Context`] per
/// thread/async task and to propagate updates to the [`Config`] using
/// message-passing style between the tasks. The library provides no
/// such functionality. Live-reloading is implemented by the program
/// itself.
pub struct Context <S> { pub struct Context <S> {
/// The configuration.
pub config: Config, pub config: Config,
/// The signing key used by actions running within this context.
pub signer: Arc<S>, pub signer: Arc<S>,
/// A handle to the database.
pub client: db::Client, pub client: db::Client,
} }
@ -111,6 +121,15 @@ mod ctx {
impl<S> Context<S> { impl<S> Context<S> {
pub async fn dereference (&self, json: Value) -> Result<Activity>
where
S: Sign
{
self.dereferencer()
.dereference(json)
.await
}
/// Attempt an action within the context of the database. /// Attempt an action within the context of the database.
pub async fn with_db <'a, F, O, T> (&'a mut self, f: F) -> Result<T> pub async fn with_db <'a, F, O, T> (&'a mut self, f: F) -> Result<T>
where where
@ -126,7 +145,7 @@ mod ctx {
} }
/// Get a dereferencer. /// Get a dereferencer.
pub fn dereferencer (&self) -> Dereferencer<S> fn dereferencer (&self) -> Dereferencer<S>
where where
S: Sign S: Sign
{ {
@ -146,10 +165,6 @@ mod ctx {
&self.config &self.config
} }
pub fn config_mut (&mut self) -> &mut Config {
&mut self.config
}
/// Conjure an activity "from thin air" as though it were posted through a client. /// Conjure an activity "from thin air" as though it were posted through a client.
pub (crate) async fn conjure (&self, act: impl Into<Activity>) -> Result<()> { pub (crate) async fn conjure (&self, act: impl Into<Activity>) -> Result<()> {
let act = act.into(); let act = act.into();

View file

@ -1,9 +1,15 @@
use std::sync::Arc; use std::sync::Arc;
use futures::stream;
use hermit::{ Context, Error, db, sign, Activity, }; use hermit::{ Context, Error, db, sign, Activity, };
use hermit::conf::Config; use hermit::conf::Config;
use tokio::sync::RwLock; use tokio::sync::{mpsc, broadcast};
use task::Executor;
use tokio_stream::wrappers::{ReceiverStream, BroadcastStream};
/// Module that contains all the API endpoints and frontend pages
/// used by Hermit.
mod web;
#[tokio::main] #[tokio::main]
async fn main () { async fn main () {
@ -34,9 +40,57 @@ async fn main () {
}; };
// Initialize the web server. let (ap_tx, ap_rx) = mk_channel(256);
task::run(&ctx, task::Server {}); let (fe_tx, fe_rx) = mk_channel(256);
// Initialize the web server.
ctx.run (task::Server {
ap_tx,
fe_tx,
});
let (ctrl_tx, ctrl_rx) = mk_channel(256);
let (json_tx, json_rx) = mk_channel(256);
// Initialize the API preprocessor.
ctx.run (task::Api {
ctrl_tx: ctrl_tx.clone(),
json_tx,
fe_rx,
ap_rx,
});
let (auto_tx, auto_rx) = mk_channel(256);
// Initialize the task that captures IPC events.
ctx.run (task::Ipc {
auto_tx,
ctrl_tx,
});
let (ctrl_tx, _rx) = broadcast::channel(256);
ctx.run (task::Auto {
ctrl_rx: ctrl_tx.subscribe(),
auto_rx
});
ctx.run (task::Process {
ctrl_rx: ctrl_tx.subscribe(),
json_rx,
});
ctx.run (task::Ctrl {
rx: ctrl_rx,
tx: ctrl_tx,
})
}
fn mk_channel <T> (size: usize) -> (mpsc::Sender<T>, ReceiverStream<T>) {
let (tx, rx) = mpsc::channel(size);
let rx = ReceiverStream::new(rx);
(tx, rx)
} }
fn err (e: impl Into<Error>) -> Error { e.into() } fn err (e: impl Into<Error>) -> Error { e.into() }
@ -47,18 +101,29 @@ mod task {
//! streams and sinks. //! streams and sinks.
use std::pin::Pin; use std::pin::Pin;
use tokio::sync::{mpsc, broadcast};
use futures::prelude::*; use futures::prelude::*;
use serde_json::Value; use serde_json::Value;
use crate::web;
use crate::sign::Sign; use crate::sign::Sign;
use crate::{flow::Flow, Activity, ctrl::Message, Context}; use crate::{flow::Flow, Activity, ctrl::Message, Context};
/// Perform a [`Task`]. impl<S> Executor for Context<S>
pub fn run <S> (ctx: &Context<S>, task: impl Task)
where where
S: Sign + Send + Sync + 'static S: Sign + Send + Sync + 'static
{ {
let ctx: Context<S> = ctx.clone(); fn run (&self, task: impl Task) {
tokio::spawn(task.run(ctx)); let ctx: Context<S> = self.clone();
tokio::spawn(task.run(ctx));
}
}
/// Something that can execute a task.
pub trait Executor {
/// Perform a [`Task`].
fn run (&self, task: impl Task);
} }
/// A computation running indefinitely on a separate thread. /// A computation running indefinitely on a separate thread.
@ -75,7 +140,12 @@ mod task {
} }
/// The main web server. /// The main web server.
pub struct Server {} pub struct Server {
/// Transmitter for messages from the ActivityPub APIs.
pub ap_tx: mpsc::Sender<Value>,
/// Transmitter for messages from the frontend APIs.
pub fe_tx: mpsc::Sender<Value>,
}
impl Task for Server { impl Task for Server {
type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>; type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
@ -84,61 +154,127 @@ mod task {
where where
S: Sign + Send + Sync + 'static S: Sign + Send + Sync + 'static
{ {
todo!() use axum::Server;
let Self { ap_tx, fe_tx } = self;
let config = ctx.config;
Box::pin(async move {
let port = config.port;
let addr = &format!("0.0.0.0:{port}").parse().unwrap();
// Both the endpoints and the frontend (if enabled) are defined in
// the `web` module.
let service = web::service(config, ap_tx, fe_tx).into_make_service();
Server::bind(addr)
.serve(service)
.await
.unwrap()
})
} }
} }
/// API request event processing. /// API request event processing.
pub struct Api <F, A, C, P> { pub struct Api <F, A> {
/// Input stream of API request events from the frontend endpoints. /// Input stream of API request events from the frontend endpoints.
pub fe_rx: F, pub fe_rx: F,
/// Input stream of API request events from the ActivityPub /// Input stream of API request events from the ActivityPub
/// endpoints. /// endpoints.
pub ap_rx: A, pub ap_rx: A,
/// Output stream to the [`Ctrl`] task. /// Output stream to the [`Ctrl`] task.
pub ctrl_tx: C, pub ctrl_tx: mpsc::Sender<Message>,
/// Output stream to the [Activity processor pipeline][Process]. /// Output stream to the [Activity processor pipeline][Process].
pub pipe_tx: P, pub json_tx: mpsc::Sender<Flow<Value>>,
} }
impl<F, A> Task for Api<F, A>
where
F: Stream<Item = Value> + Unpin + Send + 'static,
A: Stream<Item = Value> + Unpin + Send + 'static,
{
type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
fn run <S> (self, _: Context<S>) -> Self::Future
where
S: Sign + Send + Sync + 'static
{
todo!()
}
}
/// Processes CLI commands and sends them to either the [`Auto`] task (which /// Processes CLI commands and sends them to either the [`Auto`] task (which
/// takes care of scheduling automated maintenance tasks) or the [`Ctrl`] task, /// takes care of scheduling automated maintenance tasks) or the [`Ctrl`] task,
/// which propagates control messages through the system, like live config /// which propagates control messages through the system, like live config
/// updates or shutdown messages for example. /// updates or shutdown messages for example.
pub struct Ipc <A, C> { pub struct Ipc {
/// Output stream to the [`Auto`] task. /// Output stream to the [`Auto`] task.
pub auto_tx: A, pub auto_tx: mpsc::Sender<()>,
/// Output stream to the [`Ctrl`] task. /// Output stream to the [`Ctrl`] task.
pub ctrl_tx: C, pub ctrl_tx: mpsc::Sender<Message>,
} }
impl Task for Ipc {
type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
fn run <S> (self, _: Context<S>) -> Self::Future
where
S: Sign + Send + Sync + 'static
{
todo!()
}
}
/// Delivers control messages to other running tasks. /// Delivers control messages to other running tasks.
pub struct Ctrl <A, I, S> { pub struct Ctrl <I> {
/// Message stream from the [`Api`] task. /// Message stream from the [`Api`] task.
pub api_rx: A, pub rx: I,
/// Message stream from the [`Ipc`] task.
pub ipc_rx: I,
/// Fan-out to all running tasks that are subscribed to [control messages][Ctrl]. /// Fan-out to all running tasks that are subscribed to [control messages][Ctrl].
pub tx: S, pub tx: broadcast::Sender<Message>,
} }
impl<I> Task for Ctrl<I> {
type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
fn run <S> (self, _: Context<S>) -> Self::Future
where
S: Sign + Send + Sync + 'static
{
todo!()
}
}
/// Performs automated maintenance tasks. /// Performs automated maintenance tasks.
pub struct Auto <E, C> { pub struct Auto <E> {
/// Receiver for manual job triggers received from the [`Ipc`] task. /// Receiver for manual job triggers received from the [`Ipc`] task.
pub ipc_rx: E, pub auto_rx: E,
/// Receiver for [control messages][Ctrl]. /// Receiver for [control messages][Ctrl].
pub ctrl_rx: C, pub ctrl_rx: broadcast::Receiver<Message>,
} }
pub struct Process <D, C> { impl<E> Task for Auto<E> {
pub data_rx: D, type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
pub ctrl_rx: C,
fn run <S> (self, _: Context<S>) -> Self::Future
where
S: Sign + Send + Sync + 'static
{
todo!()
}
}
/// Processes incoming [`Activity`] objects.
pub struct Process <J> {
pub json_rx: J,
/// Receiver for [control messages][Ctrl].
pub ctrl_rx: broadcast::Receiver<Message>,
} }
impl<D, C> Task for Process<D, C> impl<J> Task for Process<J>
where where
D: Stream<Item = Flow<Value>> + Unpin + Send + 'static, J: Stream<Item = Flow<Value>> + Unpin + Send + 'static,
C: Stream<Item = Message> + Unpin + Send + 'static,
{ {
type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>; type Future = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
@ -147,25 +283,24 @@ mod task {
S: Sign + Send + Sync + 'static S: Sign + Send + Sync + 'static
{ {
let Self { mut data_rx, mut ctrl_rx } = self; let Self { mut json_rx, mut ctrl_rx } = self;
Box::pin(async move { Box::pin(async move {
loop { loop {
tokio::select! { tokio::select! {
// Await control commands from `Ctrl`. // Await control commands from `Ctrl`.
Some (message) = ctrl_rx.next() => match message { Ok (message) = ctrl_rx.recv() => match message {
// Live config reloading. // Live config reloading.
Message::Reconfigure (c) => c(&mut ctx.config), Message::Reconfigure (c) => c(&mut ctx.config),
// Graceful termination command from `Ctrl`. // Graceful termination command from `Ctrl`.
Message::Terminate => break, Message::Terminate => break,
}, },
// Listen for incoming activities. // Listen for incoming activities.
Some (data) = data_rx.next() => { Some (data) = json_rx.next() => {
// Dereferencing and other unfucking. // Dereferencing and other unfucking.
let d = ctx.dereferencer(); let data = match data.apply(|j| ctx.dereference(j)).await {
let data = match data.apply(|j| d.dereference(j)).await {
Ok (data) => data, Ok (data) => data,
Err (err) => { Err (err) => {
// If dereferencing fails, that sucks but it's not // If dereferencing fails, that sucks but it's not

10
src/web.rs Normal file
View file

@ -0,0 +1,10 @@
use axum::Router;
use serde_json::Value;
use tokio::sync::mpsc::Sender;
use crate::Config;
/// Create a new web service.
pub fn service (config: Config, ap_tx: Sender<Value>, fe_tx: Sender<Value>) -> Router {
Router::new()
}