use std::sync::{Arc, Mutex, OnceLock, Weak};
use crate::{
Container, ContainerRequest, Image,
core::{
containers::sync_container::{block_on_runtime, drop_shared_runtime},
error::{Error, Result},
},
runners::AsyncRunner,
};
static ASYNC_RUNTIME: OnceLock<Mutex<Weak<tokio::runtime::Runtime>>> = OnceLock::new();
pub trait SyncRunner<I: Image> {
fn start(self) -> Result<Container<I>>;
fn pull_image(self) -> Result<ContainerRequest<I>>;
}
impl<T, I> SyncRunner<I> for T
where
T: Into<ContainerRequest<I>> + Send,
I: Image,
{
fn start(self) -> Result<Container<I>> {
let runtime = shared_runtime()?;
match block_on_runtime(&runtime, AsyncRunner::start(self)) {
Ok(Ok(async_container)) => Ok(Container::new(runtime, async_container)),
Ok(Err(e)) | Err(e) => {
drop_shared_runtime(runtime);
Err(e)
}
}
}
fn pull_image(self) -> Result<ContainerRequest<I>> {
let runtime = shared_runtime()?;
let result = block_on_runtime(&runtime, AsyncRunner::pull_image(self));
drop_shared_runtime(runtime);
match result {
Ok(Ok(req)) => Ok(req),
Ok(Err(e)) | Err(e) => Err(e),
}
}
}
fn shared_runtime() -> Result<Arc<tokio::runtime::Runtime>> {
let mut guard = ASYNC_RUNTIME
.get_or_init(|| Mutex::new(Weak::new()))
.lock()
.map_err(|e| Error::other(format!("sync-runner runtime mutex poisoned: {e}")))?;
match guard.upgrade() {
Some(rt) => Ok(rt),
None => {
let rt = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.map_err(Error::other)?,
);
*guard = Arc::downgrade(&rt);
Ok(rt)
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use tokio::runtime::Runtime;
use super::shared_runtime;
#[test]
fn shared_runtime_shares_runtime_then_rebuilds_after_drop() {
let first: Arc<Runtime> = shared_runtime().expect("1 回目の Runtime 構築に失敗した");
let second: Arc<Runtime> = shared_runtime().expect("2 回目の Runtime 取得に失敗した");
assert!(
Arc::ptr_eq(&first, &second),
"2 回目の呼び出しで同一 Runtime が返されること"
);
let weak = Arc::downgrade(&first);
drop(first);
drop(second);
assert!(
weak.upgrade().is_none(),
"全ての強参照が drop された後は Weak が None になること"
);
let third: Arc<Runtime> = shared_runtime().expect("3 回目の Runtime 再構築に失敗した");
let value = third.block_on(async { 42 });
assert_eq!(value, 42, "再構築した Runtime で block_on が動くこと");
}
}