mod common;
use std::{
pin::Pin,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
task::{Context, Poll},
time::{Duration, Instant},
};
use common::RunningWorker;
use serde::{Deserialize, Serialize};
use steda::{
Error, Result, Steda, Task, TaskContext,
middleware::{Layer, Request, Response, Service},
};
#[derive(Debug, Deserialize, Serialize)]
struct RenderPreviewInput {
document_id: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct RenderPreviewOutput {
preview_path: String,
}
const RENDER_PREVIEW: Task<RenderPreviewInput, RenderPreviewOutput> = Task::new("render-preview");
#[derive(Clone, Debug)]
struct ExecutionTimingLayer {
completed_calls: Arc<AtomicU64>,
}
impl<S> Layer<S> for ExecutionTimingLayer {
type Service = ExecutionTimingService<S>;
fn layer(&self, inner: S) -> Self::Service {
ExecutionTimingService { inner, completed_calls: Arc::clone(&self.completed_calls) }
}
}
#[derive(Clone, Debug)]
struct ExecutionTimingService<S> {
inner: S,
completed_calls: Arc<AtomicU64>,
}
impl<S> Service<Request> for ExecutionTimingService<S>
where
S: Service<Request, Response = Response, Error = Error>,
S::Future: Send + 'static,
{
type Response = Response;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'static>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
let task_name = request.task_name().to_owned();
let attempt = request.attempt();
let started = Instant::now();
let future = self.inner.call(request);
let completed_calls = Arc::clone(&self.completed_calls);
Box::pin(async move {
let result = future.await;
let _ = completed_calls.fetch_add(1, Ordering::Relaxed);
println!(
"middleware observed {task_name} attempt {attempt}: {} in {} ms",
if result.is_ok() { "succeeded" } else { "failed" },
started.elapsed().as_millis()
);
result
})
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let base = common::connect().await?;
let completed_calls = Arc::new(AtomicU64::new(0));
let steda = Steda::builder(base.pool().clone())
.layer(ExecutionTimingLayer { completed_calls: Arc::clone(&completed_calls) })
.build();
let queue = steda.queue("example-tower-layer")?;
queue.create().await?;
let worker = queue
.worker()
.task(RENDER_PREVIEW, async |input: RenderPreviewInput, _ctx: TaskContext| {
tokio::time::sleep(Duration::from_millis(50)).await;
Ok(RenderPreviewOutput { preview_path: format!("previews/{}.png", input.document_id) })
})
.build()?;
let worker = RunningWorker::start(worker);
let task = queue
.spawn(RENDER_PREVIEW, RenderPreviewInput { document_id: "document-1001".to_owned() })
.await?;
let output = task.result_with_timeout(Duration::from_secs(10)).await?;
worker.stop().await?;
println!("preview created: {}", output.preview_path);
println!("middleware calls: {}", completed_calls.load(Ordering::Relaxed));
Ok(())
}