use std::cell::{Cell, RefCell};
use std::future::{Future, poll_fn};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Wake, Waker};
type ResultFuture<T> = Pin<Box<dyn Future<Output = T>>>;
type ResultFactory<T> = Box<dyn FnOnce() -> ResultFuture<T>>;
enum State<T> {
Initial(Option<ResultFactory<T>>),
Polling(ResultFuture<T>),
Ready(T),
Disposed,
}
#[derive(Default)]
struct ResultWake {
waiters: Mutex<Vec<Waker>>,
}
impl ResultWake {
fn register(&self, waker: &Waker) {
let mut waiters = self.waiters.lock().expect("result waiters mutex poisoned");
if !waiters.iter().any(|waiter| waiter.will_wake(waker)) {
waiters.push(waker.clone());
}
}
fn wake_waiters(&self) {
let waiters =
std::mem::take(&mut *self.waiters.lock().expect("result waiters mutex poisoned"));
for waiter in waiters {
waiter.wake();
}
}
}
impl Wake for ResultWake {
fn wake(self: Arc<Self>) {
self.wake_waiters();
}
fn wake_by_ref(self: &Arc<Self>) {
self.wake_waiters();
}
}
pub(crate) struct InvocationResultDriver<T> {
state: RefCell<State<T>>,
wake: Arc<ResultWake>,
source_waker: Waker,
detached: Cell<bool>,
dispose: RefCell<Option<Box<dyn FnOnce()>>>,
}
impl<T: Clone> InvocationResultDriver<T> {
pub(crate) fn new(factory: impl FnOnce() -> ResultFuture<T> + 'static) -> Self {
Self::with_disposal(factory, || {})
}
pub(crate) fn with_disposal(
factory: impl FnOnce() -> ResultFuture<T> + 'static,
dispose: impl FnOnce() + 'static,
) -> Self {
let wake = Arc::new(ResultWake::default());
Self {
state: RefCell::new(State::Initial(Some(Box::new(factory)))),
source_waker: Waker::from(Arc::clone(&wake)),
wake,
detached: Cell::new(false),
dispose: RefCell::new(Some(Box::new(dispose))),
}
}
#[cfg_attr(not(feature = "export_golem_agentic"), allow(dead_code))]
pub(crate) fn detach(&self) -> bool {
if self.detached.replace(true) {
return false;
}
let mut state = self.state.borrow_mut();
if matches!(*state, State::Ready(_)) {
*state = State::Disposed;
drop(state);
self.dispose();
}
self.wake.wake_waiters();
true
}
fn dispose(&self) {
if let Some(dispose) = self.dispose.borrow_mut().take() {
dispose();
}
}
fn poll_progress(&self, cx: &mut Context<'_>) -> Poll<()> {
loop {
let mut state = self.state.borrow_mut();
match &mut *state {
State::Initial(factory) => {
let future = factory
.take()
.expect("tool invocation result driver starts only once")(
);
*state = State::Polling(future);
}
State::Polling(future) => {
self.wake.register(cx.waker());
let mut source_context = Context::from_waker(&self.source_waker);
let Poll::Ready(result) = future.as_mut().poll(&mut source_context) else {
return Poll::Pending;
};
if self.detached.get() {
drop(result);
*state = State::Disposed;
} else {
*state = State::Ready(result);
}
drop(state);
if self.detached.get() {
self.dispose();
}
self.wake.wake_waiters();
return Poll::Ready(());
}
State::Ready(_) | State::Disposed => return Poll::Ready(()),
}
}
}
#[cfg_attr(not(feature = "export_golem_agentic"), allow(dead_code))]
pub(crate) async fn drive(self: Rc<Self>) {
poll_fn(|cx| self.poll_progress(cx)).await
}
pub(crate) async fn delivery(self: Rc<Self>) -> Option<T> {
poll_fn(|cx| {
if self.detached.get() {
return Poll::Ready(None);
}
self.poll_progress(cx)
.map(|()| match &*self.state.borrow() {
State::Ready(result) => Some(result.clone()),
_ => None,
})
})
.await
}
pub(crate) async fn wait(self: Rc<Self>) -> T {
self.delivery().await.expect("result observer is attached")
}
}