use crate::ctx::{ChildOptions, Ctx};
use crate::error::Result;
use crate::runtime::{ParentClose, Registry, RunOutcome, WorkflowFn};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub const SLOT_TYPE: &str = "fusee.queued";
pub const COORDINATOR_TYPE: &str = "fusee.concurrency";
pub fn slot_id(id: &str) -> String {
format!("{SLOT_TYPE}/{id}")
}
pub fn coordinator_id(wtype: &str, key: Option<&str>) -> String {
match key {
Some(k) => format!("{COORDINATOR_TYPE}/{wtype}/{k}"),
None => format!("{COORDINATOR_TYPE}/{wtype}"),
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Queued {
#[serde(rename = "type")]
pub wtype: String,
pub id: String,
pub input: Value,
pub limit: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option<u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Event {
kind: String,
id: String,
limit: u32,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Turns {
pub limit: u32,
pub queue: VecDeque<String>,
pub running: Vec<String>,
#[serde(default)]
events: u32,
}
const EVENT: &str = "event";
const ADMIT: &str = "admit";
const EVENTS_PER_RUN: u32 = 300;
async fn slot(ctx: Ctx, q: Queued) -> Result<Value> {
let coordinator = coordinator_id(&q.wtype, q.key.as_deref());
let limit = q.limit.max(1);
ctx.run_start_child(
COORDINATOR_TYPE,
&coordinator,
&Turns {
limit,
..Default::default()
},
ChildOptions {
close: ParentClose::Abandon,
timeout: None,
},
)?;
let ask = |kind: &str| Event {
kind: kind.into(),
id: q.id.clone(),
limit,
};
ctx.signal_workflow(&coordinator, EVENT, &ask("request"))?;
if let Err(e) = ctx.wait_for::<bool>(ADMIT).await {
ctx.signal_workflow(&coordinator, EVENT, &ask("release"))?;
return Err(e);
}
let out = ctx
.run_child::<_, Value>(
&q.wtype,
&q.id,
&q.input,
ChildOptions {
close: ParentClose::Abandon,
timeout: q.timeout_ms.map(std::time::Duration::from_millis),
},
)
.await;
ctx.signal_workflow(&coordinator, EVENT, &ask("release"))?;
out
}
async fn coordinator(ctx: Ctx, mut t: Turns) -> Result<Value> {
loop {
while (t.running.len() as u32) < t.limit.max(1) {
let Some(id) = t.queue.pop_front() else { break };
ctx.signal_workflow(&slot_id(&id), ADMIT, &true)?;
t.running.push(id);
}
if t.events >= EVENTS_PER_RUN {
t.events = 0;
return ctx.continue_as_new(&t).await;
}
let Some(e) = ctx.wait_for::<Event>(EVENT).await? else {
continue;
};
t.events += 1;
t.limit = e.limit.max(1);
match e.kind.as_str() {
"request" if !t.running.contains(&e.id) && !t.queue.contains(&e.id) => {
t.queue.push_back(e.id)
}
"release" => {
t.running.retain(|r| r != &e.id);
t.queue.retain(|r| r != &e.id);
}
_ => {}
}
}
}
fn workflow<I, F, Fut>(f: F) -> WorkflowFn
where
I: serde::de::DeserializeOwned + 'static,
F: Fn(Ctx, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + 'static,
{
let f = Arc::new(f);
Arc::new(move |ctx: Ctx| {
let f = f.clone();
let i = ctx.input_bytes(crate::ctx::decode::<I>);
Box::pin(async move {
let i: I = i.map_err(crate::engine::outcome_of)?;
let out = f(ctx, i).await.map_err(crate::engine::outcome_of)?;
crate::ctx::encode(&out).map_err(crate::engine::outcome_of)
}) as Pin<Box<dyn Future<Output = RunOutcome>>>
})
}
pub(crate) fn register(r: &mut Registry) {
r.register(SLOT_TYPE, 1, workflow(slot));
r.register(COORDINATOR_TYPE, 1, workflow(coordinator));
}