use std::{
cell::RefCell,
panic,
sync::{Arc, Once, Weak},
};
use drop_bomb::DropBomb;
use futures::Future;
use pyo3::prelude::*;
use super::{hyperion_init, RuntimeMethods};
static INITIALIZED_PYTHON: Once = Once::new();
thread_local! {
static CONTEXT: RefCell<Option<Context>> = RefCell::new(None);
}
pub struct Context {
tstate: *mut pyo3::ffi::PyThreadState,
methods: Weak<dyn RuntimeMethods>,
bomb: DropBomb,
}
impl Context {
unsafe fn new(_py: Python, methods: Weak<dyn RuntimeMethods>) -> Result<Self, ()> {
let main_state = pyo3::ffi::PyEval_SaveThread();
pyo3::ffi::PyEval_RestoreThread(main_state);
let tstate = pyo3::ffi::Py_NewInterpreter();
pyo3::ffi::PyThreadState_Swap(main_state);
if tstate.is_null() {
Err(())
} else {
Ok(Self {
tstate,
methods,
bomb: DropBomb::new("Context::release must be called before dropping it"),
})
}
}
unsafe fn release(&mut self, _py: Python) {
let main_thread = pyo3::ffi::PyThreadState_Swap(self.tstate);
pyo3::ffi::Py_EndInterpreter(self.tstate);
pyo3::ffi::PyThreadState_Swap(main_thread);
self.bomb.defuse();
}
pub fn run<U>(&self, _py: Python, f: impl FnOnce() -> U) -> U {
unsafe {
let main_state = pyo3::ffi::PyThreadState_Swap(self.tstate);
let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
pyo3::ffi::PyThreadState_Swap(main_state);
match result {
Ok(result) => result,
Err(panic) => panic::panic_any(panic),
}
}
}
pub fn with<U>(methods: Arc<dyn RuntimeMethods>, f: impl FnOnce(&Self) -> U) -> U {
unsafe {
INITIALIZED_PYTHON.call_once(|| {
pyo3::ffi::PyImport_AppendInittab(
b"hyperion\0".as_ptr() as *const _,
Some(hyperion_init),
);
pyo3::prepare_freethreaded_python();
});
let result = CONTEXT.with(|ctx| {
*ctx.borrow_mut() = Some(Python::with_gil(|py| {
Self::new(py, Arc::downgrade(&methods))
.expect("failed initializing python subinterp")
}));
let result = {
let borrow = ctx.borrow();
let ctx = borrow.as_ref().unwrap();
panic::catch_unwind(panic::AssertUnwindSafe(|| f(ctx)))
};
if let Some(mut ctx) = ctx.borrow_mut().take() {
Python::with_gil(|py| {
ctx.release(py);
})
}
result
});
match result {
Ok(result) => result,
Err(panic) => panic::panic_any(panic),
}
}
}
pub fn with_current<F, U>(f: impl FnOnce(Arc<dyn RuntimeMethods>) -> F) -> U
where
F: Future<Output = U>,
{
CONTEXT.with(|ctx| {
futures::executor::block_on(f(ctx
.borrow()
.as_ref()
.expect("no current context")
.methods
.upgrade()
.expect("no current methods")))
})
}
}