Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ use thiserror::Error;

#[derive(Debug, Error)]
pub enum BraintrustError {
#[error(transparent)]
Execution(#[from] crate::ExecutionError),
#[error(transparent)]
Admission(#[from] crate::EnqueueError),
#[error("invalid configuration: {0}")]
InvalidConfig(String),
#[error("http error: {0}")]
Expand Down
137 changes: 84 additions & 53 deletions src/internal/delivery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ use std::{
time::Duration,
};
use tokio::{
runtime::Handle,
sync::{oneshot, Notify, Semaphore},
task::AbortHandle,
};
use tracing::warn;

use crate::internal::write_outcome::{WriteOutcome, WriteResponder};
use crate::internal::execution::ExecutionBinding;
use crate::internal::shared_outcome::{WriteOutcome, WriteResponder};
type WriteSender = WriteResponder;
type FlushSender = oneshot::Sender<Result<FlushReport, FlushError>>;
#[derive(Clone)]
Expand Down Expand Up @@ -215,7 +215,7 @@ impl<C> Inner<C> {
pub(crate) struct WriteService<C> {
inner: Arc<Inner<C>>,
owners: Arc<ProducerOwner<C>>,
runtime: Handle,
runtime: ExecutionBinding,
exit: Shared<BoxFuture<'static, Result<(), ExecutionError>>>,
}
impl<C> Clone for WriteService<C> {
Expand All @@ -239,7 +239,7 @@ impl<C> Drop for ProducerOwner<C> {
}
}
impl<C: Send + 'static> WriteService<C> {
pub fn new<F>(capacity: usize, runtime: Handle, process: F) -> Self
pub fn new<F>(capacity: usize, runtime: ExecutionBinding, process: F) -> Self
where
F: Fn(Vec<C>) -> BoxFuture<'static, anyhow::Result<()>> + Send + 'static,
{
Expand Down Expand Up @@ -394,6 +394,13 @@ impl<C: Send + 'static> WriteService<C> {
completion
}

pub fn cancel(&self, error: ExecutionError) {
self.inner.stop(error);
if let Some(abort) = lock(&self.inner.abort).as_ref() {
abort.abort();
}
}

#[cfg(test)]
pub fn is_empty(&self) -> bool {
lock(&self.inner.state).outstanding == 0
Expand Down Expand Up @@ -472,15 +479,19 @@ mod tests {
let (entered, receiver) = mpsc::unbounded_channel();
let gate = Arc::new(Semaphore::new(0));
let process_gate = gate.clone();
let service = WriteService::new(capacity, Handle::current(), move |commands| {
let gate = process_gate.clone();
let entered = entered.clone();
Box::pin(async move {
entered.send(commands).unwrap();
gate.acquire().await.unwrap().forget();
Ok(())
})
});
let service = WriteService::new(
capacity,
ExecutionBinding::external(tokio::runtime::Handle::current()),
move |commands| {
let gate = process_gate.clone();
let entered = entered.clone();
Box::pin(async move {
entered.send(commands).unwrap();
gate.acquire().await.unwrap().forget();
Ok(())
})
},
);
(service, receiver, gate)
}

Expand Down Expand Up @@ -516,18 +527,22 @@ mod tests {
let (entered, mut receiver) = mpsc::unbounded_channel();
let gate = Arc::new(Semaphore::new(0));
let process_gate = gate.clone();
let service = WriteService::new(4, Handle::current(), move |commands: Vec<usize>| {
let entered = entered.clone();
let gate = process_gate.clone();
Box::pin(async move {
entered.send(commands.clone()).unwrap();
gate.acquire().await.unwrap().forget();
if commands == [1] {
anyhow::bail!("first batch failed");
}
Ok(())
})
});
let service = WriteService::new(
4,
ExecutionBinding::external(tokio::runtime::Handle::current()),
move |commands: Vec<usize>| {
let entered = entered.clone();
let gate = process_gate.clone();
Box::pin(async move {
entered.send(commands.clone()).unwrap();
gate.acquire().await.unwrap().forget();
if commands == [1] {
anyhow::bail!("first batch failed");
}
Ok(())
})
},
);
let first = service.enqueue(1);
assert_eq!(receiver.recv().await.unwrap(), vec![1]);
let second = service.enqueue(2);
Expand All @@ -548,11 +563,15 @@ mod tests {

#[tokio::test]
async fn graceful_shutdown_reports_its_failed_batch_after_notifying_all_writes() {
let service = WriteService::new(4, Handle::current(), |_: Vec<usize>| {
Box::pin(async {
anyhow::bail!("terminal failure");
})
});
let service = WriteService::new(
4,
ExecutionBinding::external(tokio::runtime::Handle::current()),
|_: Vec<usize>| {
Box::pin(async {
anyhow::bail!("terminal failure");
})
},
);
let first = service.enqueue(1);
let second = service.enqueue(2);
let shutdown = service.shutdown(Duration::from_secs(1));
Expand Down Expand Up @@ -605,15 +624,19 @@ mod tests {
async fn every_failed_batch_notifies_all_members_releases_errors_and_allows_fresh_work() {
let fail = Arc::new(AtomicBool::new(true));
let process_fail = fail.clone();
let service = WriteService::new(4, Handle::current(), move |_: Vec<usize>| {
let fail = process_fail.load(Ordering::Relaxed);
Box::pin(async move {
if fail {
anyhow::bail!("terminal failure");
}
Ok(())
})
});
let service = WriteService::new(
4,
ExecutionBinding::external(tokio::runtime::Handle::current()),
move |_: Vec<usize>| {
let fail = process_fail.load(Ordering::Relaxed);
Box::pin(async move {
if fail {
anyhow::bail!("terminal failure");
}
Ok(())
})
},
);
for _ in 0..100 {
let first = service.enqueue(1);
let second = service.enqueue(2);
Expand Down Expand Up @@ -765,15 +788,19 @@ mod tests {
let (entered, mut receiver) = mpsc::unbounded_channel();
let gate = Arc::new(Semaphore::new(0));
let process_gate = gate.clone();
let service = WriteService::new(4, Handle::current(), move |_: Vec<usize>| {
let entered = entered.clone();
let gate = process_gate.clone();
Box::pin(async move {
entered.send(()).unwrap();
gate.acquire().await.unwrap().forget();
panic!("test processor failed");
})
});
let service = WriteService::new(
4,
ExecutionBinding::external(tokio::runtime::Handle::current()),
move |_: Vec<usize>| {
let entered = entered.clone();
let gate = process_gate.clone();
Box::pin(async move {
entered.send(()).unwrap();
gate.acquire().await.unwrap().forget();
panic!("test processor failed");
})
},
);
let active = service.enqueue(1);
receiver.recv().await.unwrap();
let pending = service.enqueue(2);
Expand Down Expand Up @@ -817,11 +844,15 @@ mod tests {
let processed = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let process_seen = seen.clone();
let process_count = processed.clone();
let service = WriteService::new(1024, Handle::current(), move |commands: Vec<usize>| {
lock(&process_seen).extend(commands.iter().copied());
process_count.fetch_add(commands.len(), Ordering::Relaxed);
Box::pin(async { Ok(()) })
});
let service = WriteService::new(
1024,
ExecutionBinding::external(tokio::runtime::Handle::current()),
move |commands: Vec<usize>| {
lock(&process_seen).extend(commands.iter().copied());
process_count.fetch_add(commands.len(), Ordering::Relaxed);
Box::pin(async { Ok(()) })
},
);
let (writes, flushes) = std::thread::scope(|scope| {
let producers: Vec<_> = (0..4)
.map(|producer| {
Expand Down
Loading
Loading