use crate::futures::{DelayedActionRunner, FutureSpawner};
use crate::instrumentation::queue::InstrumentedQueue;
use crate::instrumentation::writer::InstrumentedThreadWriterSharedPart;
use crate::messaging::Actor;
use crate::pretty_type_name;
use crate::tokio::runtime::AsyncDroppableRuntime;
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
pub(super) struct TokioRuntimeMessage<A> {
pub(super) seq: u64,
pub(super) enqueued_time_ns: u64,
pub(super) name: &'static str,
pub(super) function: Box<dyn FnOnce(&mut A, &mut dyn DelayedActionRunner<A>) + Send>,
}
pub struct TokioRuntimeHandle<A> {
sender: mpsc::UnboundedSender<TokioRuntimeMessage<A>>,
pub(super) instrumentation: Arc<InstrumentedThreadWriterSharedPart>,
pub(super) runtime_handle: tokio::runtime::Handle,
cancel: CancellationToken,
}
impl<A> Clone for TokioRuntimeHandle<A> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
instrumentation: self.instrumentation.clone(),
runtime_handle: self.runtime_handle.clone(),
cancel: self.cancel.clone(),
}
}
}
impl<A> TokioRuntimeHandle<A>
where
A: 'static,
{
pub fn sender(&self) -> Arc<TokioRuntimeHandle<A>> {
Arc::new(self.clone())
}
pub fn future_spawner(&self) -> Box<dyn FutureSpawner> {
Box::new(self.clone())
}
pub fn stop(&self) {
self.cancel.cancel();
}
}
impl<A> TokioRuntimeHandle<A> {
pub(super) fn send_message(
&self,
message: TokioRuntimeMessage<A>,
) -> Result<(), mpsc::error::SendError<TokioRuntimeMessage<A>>> {
let name = message.name;
self.sender.send(message).map(|_| {
self.instrumentation.queue().enqueue(name);
})
}
}
pub(crate) fn spawn_tokio_actor<A>(
actor: A,
actor_name: String,
system_cancellation_signal: CancellationToken,
) -> TokioRuntimeHandle<A>
where
A: Actor + Send + 'static,
{
let runtime_builder = TokioRuntimeBuilder::new(actor_name, system_cancellation_signal);
let handle = runtime_builder.handle();
runtime_builder.spawn_tokio_actor(actor);
handle
}
struct CallStopWhenDropping<A: Actor> {
actor: A,
}
impl<A: Actor> Drop for CallStopWhenDropping<A> {
fn drop(&mut self) {
self.actor.stop_actor();
}
}
pub struct TokioRuntimeBuilder<A: Actor + Send + 'static> {
handle: TokioRuntimeHandle<A>,
receiver: Option<mpsc::UnboundedReceiver<TokioRuntimeMessage<A>>>,
shared_instrumentation: Arc<InstrumentedThreadWriterSharedPart>,
system_cancellation_signal: CancellationToken,
runtime: Option<Runtime>,
}
impl<A: Actor + Send + 'static> TokioRuntimeBuilder<A> {
pub fn new(actor_name: String, system_cancellation_signal: CancellationToken) -> Self {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
let (sender, receiver) = mpsc::unbounded_channel::<TokioRuntimeMessage<A>>();
let instrumented_queue = InstrumentedQueue::new(&actor_name);
let shared_instrumentation =
InstrumentedThreadWriterSharedPart::new(actor_name, instrumented_queue);
runtime.spawn({
let shared_instrumentation = shared_instrumentation.clone();
async move {
shared_instrumentation.with_thread_local_writer(|_writer| {});
}
});
let cancel = CancellationToken::new();
let handle = TokioRuntimeHandle {
sender,
runtime_handle: runtime.handle().clone(),
cancel,
instrumentation: shared_instrumentation.clone(),
};
Self {
handle,
receiver: Some(receiver),
shared_instrumentation,
system_cancellation_signal,
runtime: Some(runtime),
}
}
pub fn handle(&self) -> TokioRuntimeHandle<A> {
self.handle.clone()
}
pub fn spawn_tokio_actor(mut self, mut actor: A) {
let mut runtime_handle = self.handle.clone();
let inner_runtime_handle = runtime_handle.runtime_handle.clone();
let runtime = self.runtime.take().unwrap();
let mut receiver = self.receiver.take().unwrap();
let shared_instrumentation = self.shared_instrumentation.clone();
let actor_name = pretty_type_name::<A>();
inner_runtime_handle.spawn(async move {
actor.start_actor(&mut runtime_handle);
let _runtime = AsyncDroppableRuntime::new(runtime);
let mut actor = CallStopWhenDropping { actor };
let mut window_update_timer = tokio::time::interval(Duration::from_secs(1));
loop {
tokio::select! {
_ = self.system_cancellation_signal.cancelled() => {
tracing::debug!(target: "tokio_runtime", actor_name, "shutting down tokio runtime due to actor system shutdown");
break;
}
_ = runtime_handle.cancel.cancelled() => {
tracing::debug!(target: "tokio_runtime", actor_name, "shutting down tokio runtime due to targeted cancellation");
break;
}
_ = window_update_timer.tick() => {
tracing::trace!(target: "tokio_runtime", "advancing instrumentation window");
shared_instrumentation.with_thread_local_writer(|writer| writer.advance_window_if_needed());
}
Some(message) = receiver.recv() => {
let seq = message.seq;
shared_instrumentation.queue().dequeue(message.name);
tracing::trace!(target: "tokio_runtime", seq, actor_name, "executing message");
let dequeue_time_ns = shared_instrumentation.current_time().saturating_sub(message.enqueued_time_ns);
shared_instrumentation.with_thread_local_writer(|writer| writer.start_event(message.name, dequeue_time_ns));
(message.function)(&mut actor.actor, &mut runtime_handle);
shared_instrumentation.with_thread_local_writer(|writer| writer.end_event(message.name));
}
}
}
});
}
}
impl<A> Drop for TokioRuntimeBuilder<A>
where
A: Actor + Send + 'static,
{
fn drop(&mut self) {
if self.runtime.is_some() {
panic!(
"TokioRuntimeBuilder must be built before dropping. Did you forget to call spawn_tokio_actor?"
);
}
}
}