|
@@ -1,28 +1,28 @@
|
|
#![feature(impl_trait_in_assoc_type)]
|
|
#![feature(impl_trait_in_assoc_type)]
|
|
|
|
|
|
use std::{
|
|
use std::{
|
|
- any::Any,
|
|
|
|
collections::{hash_map, HashMap},
|
|
collections::{hash_map, HashMap},
|
|
fmt::Display,
|
|
fmt::Display,
|
|
future::{ready, Future, Ready},
|
|
future::{ready, Future, Ready},
|
|
marker::PhantomData,
|
|
marker::PhantomData,
|
|
net::IpAddr,
|
|
net::IpAddr,
|
|
ops::DerefMut,
|
|
ops::DerefMut,
|
|
|
|
+ panic::AssertUnwindSafe,
|
|
pin::Pin,
|
|
pin::Pin,
|
|
- sync::Arc,
|
|
|
|
|
|
+ sync::{Arc, Mutex as SyncMutex},
|
|
};
|
|
};
|
|
|
|
|
|
use btlib::{bterr, crypto::Creds, error::StringError, BlockPath, Result};
|
|
use btlib::{bterr, crypto::Creds, error::StringError, BlockPath, Result};
|
|
use btserde::{from_slice, to_vec, write_to};
|
|
use btserde::{from_slice, to_vec, write_to};
|
|
-use bttp::{DeserCallback, MsgCallback, Receiver, Replier, Transmitter};
|
|
|
|
-use kernel::{kernel, SpawnReq};
|
|
|
|
|
|
+use bttp::{DeserCallback, MsgCallback, Replier, Transmitter};
|
|
|
|
+use futures::FutureExt;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde::{Deserialize, Serialize};
|
|
-use tokio::{
|
|
|
|
- sync::{mpsc, oneshot, Mutex, RwLock},
|
|
|
|
- task::AbortHandle,
|
|
|
|
-};
|
|
|
|
|
|
+use tokio::sync::{mpsc, Mutex, RwLock};
|
|
|
|
|
|
|
|
+pub use bttp::Receiver;
|
|
mod kernel;
|
|
mod kernel;
|
|
|
|
+pub use kernel::SpawnReq;
|
|
|
|
+use kernel::{kernel, FaultResult};
|
|
pub mod model;
|
|
pub mod model;
|
|
use model::*;
|
|
use model::*;
|
|
|
|
|
|
@@ -32,17 +32,17 @@ use model::*;
|
|
#[macro_export]
|
|
#[macro_export]
|
|
macro_rules! declare_runtime {
|
|
macro_rules! declare_runtime {
|
|
($name:ident, $ip_addr:expr, $creds:expr) => {
|
|
($name:ident, $ip_addr:expr, $creds:expr) => {
|
|
- ::lazy_static::lazy_static! {
|
|
|
|
- static ref $name: &'static $crate::Runtime = {
|
|
|
|
- ::lazy_static::lazy_static! {
|
|
|
|
- static ref RUNTIME: $crate::Runtime = $crate::Runtime::_new($creds).unwrap();
|
|
|
|
- static ref RECEIVER: ::bttp::Receiver = _new_receiver($ip_addr, $creds, &*RUNTIME);
|
|
|
|
- }
|
|
|
|
- // By dereferencing RECEIVER we ensure it is started.
|
|
|
|
- let _ = &*RECEIVER;
|
|
|
|
- &*RUNTIME
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
|
|
+ static $name: $crate::model::Lazy<&'static $crate::Runtime> = $crate::model::Lazy::new(|| {
|
|
|
|
+ use $crate::{Runtime, Receiver, model::Lazy};
|
|
|
|
+ static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::_new($creds).unwrap());
|
|
|
|
+ static RECEIVER: Lazy<Receiver> =
|
|
|
|
+ Lazy::new(|| _new_receiver($ip_addr, $creds, &*RUNTIME));
|
|
|
|
+ // Start the kernel task.
|
|
|
|
+ RUNTIME._spawn_kernel();
|
|
|
|
+ // By dereferencing RECEIVER we ensure it's started.
|
|
|
|
+ let _ = &*RECEIVER;
|
|
|
|
+ &*RUNTIME
|
|
|
|
+ });
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
@@ -71,6 +71,7 @@ pub struct Runtime {
|
|
peers: RwLock<HashMap<Arc<BlockPath>, Transmitter>>,
|
|
peers: RwLock<HashMap<Arc<BlockPath>, Transmitter>>,
|
|
registry: RwLock<HashMap<ServiceId, ServiceRecord>>,
|
|
registry: RwLock<HashMap<ServiceId, ServiceRecord>>,
|
|
kernel_sender: mpsc::Sender<SpawnReq>,
|
|
kernel_sender: mpsc::Sender<SpawnReq>,
|
|
|
|
+ kernel_receiver: SyncMutex<Option<mpsc::Receiver<SpawnReq>>>,
|
|
}
|
|
}
|
|
|
|
|
|
impl Runtime {
|
|
impl Runtime {
|
|
@@ -86,16 +87,25 @@ impl Runtime {
|
|
pub fn _new<C: 'static + Send + Sync + Creds>(creds: Arc<C>) -> Result<Runtime> {
|
|
pub fn _new<C: 'static + Send + Sync + Creds>(creds: Arc<C>) -> Result<Runtime> {
|
|
let path = Arc::new(creds.bind_path()?);
|
|
let path = Arc::new(creds.bind_path()?);
|
|
let (sender, receiver) = mpsc::channel(Self::SPAWN_REQ_BUF_SZ);
|
|
let (sender, receiver) = mpsc::channel(Self::SPAWN_REQ_BUF_SZ);
|
|
- tokio::task::spawn(kernel(receiver));
|
|
|
|
Ok(Runtime {
|
|
Ok(Runtime {
|
|
path,
|
|
path,
|
|
handles: RwLock::new(HashMap::new()),
|
|
handles: RwLock::new(HashMap::new()),
|
|
peers: RwLock::new(HashMap::new()),
|
|
peers: RwLock::new(HashMap::new()),
|
|
registry: RwLock::new(HashMap::new()),
|
|
registry: RwLock::new(HashMap::new()),
|
|
kernel_sender: sender,
|
|
kernel_sender: sender,
|
|
|
|
+ kernel_receiver: SyncMutex::new(Some(receiver)),
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// This method is not intended to be called directly by downstream crates. It's used
|
|
|
|
+ /// by the [declare_runtime] macro to start a runtime's kernel task. If you call it,
|
|
|
|
+ /// it will panic.
|
|
|
|
+ #[doc(hidden)]
|
|
|
|
+ pub fn _spawn_kernel(&'static self) {
|
|
|
|
+ let receiver = self.kernel_receiver.lock().unwrap().take().unwrap();
|
|
|
|
+ tokio::task::spawn(kernel(self, receiver));
|
|
|
|
+ }
|
|
|
|
+
|
|
pub fn path(&self) -> &Arc<BlockPath> {
|
|
pub fn path(&self) -> &Arc<BlockPath> {
|
|
&self.path
|
|
&self.path
|
|
}
|
|
}
|
|
@@ -106,8 +116,21 @@ impl Runtime {
|
|
guard.len()
|
|
guard.len()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ pub(crate) async fn send_control(&self, to: ActorName, msg: ControlMsg) -> Result<()> {
|
|
|
|
+ if to.path().as_ref() == self.path.as_ref() {
|
|
|
|
+ let handles = self.handles.read().await;
|
|
|
|
+ if let Some(handle) = handles.get(&to.act_id()) {
|
|
|
|
+ handle.send_control(msg).await
|
|
|
|
+ } else {
|
|
|
|
+ Err(RuntimeError::BadActorName(to).into())
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ todo!("Deliver the message remotely.");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Sends a message to the actor identified by the given [ActorName].
|
|
/// Sends a message to the actor identified by the given [ActorName].
|
|
- pub async fn send<T: 'static + SendMsg>(
|
|
|
|
|
|
+ pub async fn send<T: 'static + MsgEnum>(
|
|
&self,
|
|
&self,
|
|
to: ActorName,
|
|
to: ActorName,
|
|
from: ActorName,
|
|
from: ActorName,
|
|
@@ -118,7 +141,7 @@ impl Runtime {
|
|
if let Some(handle) = guard.get(&to.act_id()) {
|
|
if let Some(handle) = guard.get(&to.act_id()) {
|
|
handle.send(from, msg).await
|
|
handle.send(from, msg).await
|
|
} else {
|
|
} else {
|
|
- Err(bterr!("invalid actor name"))
|
|
|
|
|
|
+ Err(RuntimeError::BadActorName(to).into())
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
let guard = self.peers.read().await;
|
|
let guard = self.peers.read().await;
|
|
@@ -137,7 +160,7 @@ impl Runtime {
|
|
}
|
|
}
|
|
|
|
|
|
/// Sends a message to the service identified by [ServiceName].
|
|
/// Sends a message to the service identified by [ServiceName].
|
|
- pub async fn send_service<T: 'static + SendMsg>(
|
|
|
|
|
|
+ pub async fn send_service<T: 'static + MsgEnum>(
|
|
&'static self,
|
|
&'static self,
|
|
to: ServiceAddr,
|
|
to: ServiceAddr,
|
|
from: ActorName,
|
|
from: ActorName,
|
|
@@ -162,7 +185,7 @@ impl Runtime {
|
|
|
|
|
|
/// Sends a message to the actor identified by the given [ActorName] and returns a future which
|
|
/// Sends a message to the actor identified by the given [ActorName] and returns a future which
|
|
/// is ready when a reply has been received.
|
|
/// is ready when a reply has been received.
|
|
- pub async fn call<T: 'static + CallMsg>(
|
|
|
|
|
|
+ pub async fn call<T: 'static + MsgEnum>(
|
|
&self,
|
|
&self,
|
|
to: ActorName,
|
|
to: ActorName,
|
|
from: ActorName,
|
|
from: ActorName,
|
|
@@ -192,7 +215,7 @@ impl Runtime {
|
|
}
|
|
}
|
|
|
|
|
|
/// Calls a service identified by [ServiceName].
|
|
/// Calls a service identified by [ServiceName].
|
|
- pub async fn call_service<T: 'static + CallMsg>(
|
|
|
|
|
|
+ pub async fn call_service<T: 'static + MsgEnum>(
|
|
&'static self,
|
|
&'static self,
|
|
to: ServiceAddr,
|
|
to: ServiceAddr,
|
|
msg: T,
|
|
msg: T,
|
|
@@ -251,16 +274,16 @@ impl Runtime {
|
|
}
|
|
}
|
|
|
|
|
|
/// Spawns a new actor using the given activator function and returns a handle to it.
|
|
/// Spawns a new actor using the given activator function and returns a handle to it.
|
|
- pub async fn spawn<Msg, F, Fut>(&'static self, activator: F) -> ActorName
|
|
|
|
|
|
+ pub async fn spawn<Msg, F, Fut>(&'static self, owner: Option<ActorName>, actor: F) -> ActorName
|
|
where
|
|
where
|
|
- Msg: 'static + CallMsg,
|
|
|
|
- Fut: 'static + Send + Future<Output = ()>,
|
|
|
|
- F: FnOnce(&'static Runtime, Mailbox<Msg>, ActorId) -> Fut,
|
|
|
|
|
|
+ Msg: 'static + MsgEnum,
|
|
|
|
+ Fut: 'static + Send + Future<Output = ActorResult>,
|
|
|
|
+ F: FnOnce(Mailbox<Msg>, ActorId, &'static Runtime) -> Fut,
|
|
{
|
|
{
|
|
- let mut guard = self.handles.write().await;
|
|
|
|
|
|
+ let mut handles = self.handles.write().await;
|
|
let act_id = {
|
|
let act_id = {
|
|
let mut act_id = ActorId::new();
|
|
let mut act_id = ActorId::new();
|
|
- while guard.contains_key(&act_id) {
|
|
|
|
|
|
+ while handles.contains_key(&act_id) {
|
|
act_id = ActorId::new();
|
|
act_id = ActorId::new();
|
|
}
|
|
}
|
|
act_id
|
|
act_id
|
|
@@ -307,7 +330,21 @@ impl Runtime {
|
|
fut
|
|
fut
|
|
}
|
|
}
|
|
};
|
|
};
|
|
- let (req, receiver) = SpawnReq::new(activator(self, rx, act_id));
|
|
|
|
|
|
+ // ctrl_deliverer is responsible for delivering ControlMsgs to the actor.
|
|
|
|
+ let ctrl_deliverer = {
|
|
|
|
+ let tx = tx.clone();
|
|
|
|
+ move |control: ControlMsg| {
|
|
|
|
+ let tx = tx.clone();
|
|
|
|
+ let fut: FutureResult = Box::pin(async move {
|
|
|
|
+ tx.send(Envelope::Control(control))
|
|
|
|
+ .await
|
|
|
|
+ .map_err(|err| bterr!("{err}"))
|
|
|
|
+ });
|
|
|
|
+ fut
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ let actor = actor(rx, act_id, self);
|
|
|
|
+ let (req, receiver) = SpawnReq::new(Self::catch_unwind(act_id, actor));
|
|
self.kernel_sender
|
|
self.kernel_sender
|
|
.send(req)
|
|
.send(req)
|
|
.await
|
|
.await
|
|
@@ -315,11 +352,29 @@ impl Runtime {
|
|
let handle = receiver
|
|
let handle = receiver
|
|
.await
|
|
.await
|
|
.unwrap_or_else(|err| panic!("Kernel failed to send abort handle: {err}"));
|
|
.unwrap_or_else(|err| panic!("Kernel failed to send abort handle: {err}"));
|
|
- let actor_handle = ActorHandle::new(handle, tx, deliverer);
|
|
|
|
- guard.insert(act_id, actor_handle);
|
|
|
|
|
|
+ let actor_handle = ActorHandle::new(
|
|
|
|
+ act_name.clone(),
|
|
|
|
+ handle,
|
|
|
|
+ tx,
|
|
|
|
+ deliverer,
|
|
|
|
+ ctrl_deliverer,
|
|
|
|
+ owner,
|
|
|
|
+ );
|
|
|
|
+ handles.insert(act_id, actor_handle);
|
|
act_name
|
|
act_name
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ async fn catch_unwind<Fut>(actor_id: ActorId, fut: Fut) -> FaultResult
|
|
|
|
+ where
|
|
|
|
+ Fut: 'static + Send + Future<Output = ActorResult>,
|
|
|
|
+ {
|
|
|
|
+ let fut = AssertUnwindSafe(fut);
|
|
|
|
+ match fut.catch_unwind().await {
|
|
|
|
+ Ok(result) => result.map_err(ActorFault::Error),
|
|
|
|
+ Err(panic) => Err(ActorFault::Panic(ActorPanic::new(actor_id, panic))),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Registers a service activation closure for [ServiceId]. An error is returned if the
|
|
/// Registers a service activation closure for [ServiceId]. An error is returned if the
|
|
/// [ServiceId] has already been registered.
|
|
/// [ServiceId] has already been registered.
|
|
pub async fn register<Msg, F>(&self, id: ServiceId, spawner: F) -> Result<ServiceName>
|
|
pub async fn register<Msg, F>(&self, id: ServiceId, spawner: F) -> Result<ServiceName>
|
|
@@ -533,7 +588,7 @@ impl RuntimeCallback {
|
|
} else {
|
|
} else {
|
|
WireEnvelope::Send { msg }
|
|
WireEnvelope::Send { msg }
|
|
};
|
|
};
|
|
- (handle.deliverer)(envelope).await
|
|
|
|
|
|
+ handle.deliver(envelope).await
|
|
} else {
|
|
} else {
|
|
Err(bterr!("invalid actor name: {}", msg.to))
|
|
Err(bterr!("invalid actor name: {}", msg.to))
|
|
}
|
|
}
|
|
@@ -617,192 +672,46 @@ impl<'de> WireEnvelope<'de> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-pub enum EnvelopeKind<T: CallMsg> {
|
|
|
|
- Call {
|
|
|
|
- reply: Option<oneshot::Sender<T::Reply>>,
|
|
|
|
- },
|
|
|
|
- Send {
|
|
|
|
- from: ActorName,
|
|
|
|
- },
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<T: CallMsg> EnvelopeKind<T> {
|
|
|
|
- pub fn name(&self) -> &'static str {
|
|
|
|
- match self {
|
|
|
|
- Self::Call { .. } => "Call",
|
|
|
|
- Self::Send { .. } => "Send",
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Wrapper around a message type `T` which indicates who the message is from and, if the message
|
|
|
|
-/// was dispatched with `call`, provides a channel to reply to it.
|
|
|
|
-pub struct Envelope<T: CallMsg> {
|
|
|
|
- msg: T,
|
|
|
|
- kind: EnvelopeKind<T>,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<T: CallMsg> Envelope<T> {
|
|
|
|
- pub fn new(msg: T, kind: EnvelopeKind<T>) -> Self {
|
|
|
|
- Self { msg, kind }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Creates a new envelope containing the given message which does not expect a reply.
|
|
|
|
- fn new_send(from: ActorName, msg: T) -> Self {
|
|
|
|
- Self {
|
|
|
|
- kind: EnvelopeKind::Send { from },
|
|
|
|
- msg,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Creates a new envelope containing the given message which expects exactly one reply.
|
|
|
|
- fn new_call(msg: T) -> (Self, oneshot::Receiver<T::Reply>) {
|
|
|
|
- let (tx, rx) = oneshot::channel::<T::Reply>();
|
|
|
|
- let envelope = Self {
|
|
|
|
- kind: EnvelopeKind::Call { reply: Some(tx) },
|
|
|
|
- msg,
|
|
|
|
- };
|
|
|
|
- (envelope, rx)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Returns the name of the actor which sent this message.
|
|
|
|
- pub fn from(&self) -> Option<&ActorName> {
|
|
|
|
- match &self.kind {
|
|
|
|
- EnvelopeKind::Send { from } => Some(from),
|
|
|
|
- _ => None,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Returns a reference to the message in this envelope.
|
|
|
|
- pub fn msg(&self) -> &T {
|
|
|
|
- &self.msg
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Sends a reply to this message.
|
|
|
|
- ///
|
|
|
|
- /// If this message is not expecting a reply, or if this message has already been replied to,
|
|
|
|
- /// then an error is returned.
|
|
|
|
- pub fn reply(&mut self, reply: T::Reply) -> Result<()> {
|
|
|
|
- match &mut self.kind {
|
|
|
|
- EnvelopeKind::Call { reply: tx } => {
|
|
|
|
- if let Some(tx) = tx.take() {
|
|
|
|
- tx.send(reply).map_err(|_| bterr!("Failed to send reply."))
|
|
|
|
- } else {
|
|
|
|
- Err(bterr!("Reply has already been sent."))
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- _ => Err(bterr!("Can't reply to '{}' messages.", self.kind.name())),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Returns true if this message expects a reply and it has not already been replied to.
|
|
|
|
- pub fn needs_reply(&self) -> bool {
|
|
|
|
- matches!(&self.kind, EnvelopeKind::Call { .. })
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn split(self) -> (T, EnvelopeKind<T>) {
|
|
|
|
- (self.msg, self.kind)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-type FutureResult = Pin<Box<dyn Send + Future<Output = Result<()>>>>;
|
|
|
|
-
|
|
|
|
-pub struct ActorHandle {
|
|
|
|
- handle: AbortHandle,
|
|
|
|
- sender: Box<dyn Send + Sync + Any>,
|
|
|
|
- deliverer: Box<dyn Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult>,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl ActorHandle {
|
|
|
|
- fn new<T, F>(handle: AbortHandle, sender: mpsc::Sender<Envelope<T>>, deliverer: F) -> Self
|
|
|
|
- where
|
|
|
|
- T: 'static + CallMsg,
|
|
|
|
- F: 'static + Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult,
|
|
|
|
- {
|
|
|
|
- Self {
|
|
|
|
- handle,
|
|
|
|
- sender: Box::new(sender),
|
|
|
|
- deliverer: Box::new(deliverer),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn sender<T: 'static + CallMsg>(&self) -> Result<&mpsc::Sender<Envelope<T>>> {
|
|
|
|
- self.sender
|
|
|
|
- .downcast_ref::<mpsc::Sender<Envelope<T>>>()
|
|
|
|
- .ok_or_else(|| bterr!("Attempt to send message as the wrong type."))
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Sends a message to the actor represented by this handle.
|
|
|
|
- pub async fn send<T: 'static + SendMsg>(&self, from: ActorName, msg: T) -> Result<()> {
|
|
|
|
- let sender = self.sender()?;
|
|
|
|
- sender
|
|
|
|
- .send(Envelope::new_send(from, msg))
|
|
|
|
- .await
|
|
|
|
- .map_err(|_| bterr!("failed to enqueue message"))?;
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub async fn call_through<T: 'static + CallMsg>(&self, msg: T) -> Result<T::Reply> {
|
|
|
|
- let sender = self.sender()?;
|
|
|
|
- let (envelope, rx) = Envelope::new_call(msg);
|
|
|
|
- sender
|
|
|
|
- .send(envelope)
|
|
|
|
- .await
|
|
|
|
- .map_err(|_| bterr!("failed to enqueue call"))?;
|
|
|
|
- let reply = rx.await?;
|
|
|
|
- Ok(reply)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn abort(&self) {
|
|
|
|
- self.handle.abort();
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Drop for ActorHandle {
|
|
|
|
- fn drop(&mut self) {
|
|
|
|
- self.abort();
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Sets up variable declarations and logging configuration to facilitate testing with a [Runtime].
|
|
|
|
|
|
+/// Sets up static `RUNTIME` and `ASYNC_RT` static variables, with types [Runtime] and
|
|
|
|
+/// [tokio::runtime::Runtime] respectively, in the scope where its used.
|
|
|
|
+/// The `env_logger` crate is also configured to print messages to stderr.
|
|
#[macro_export]
|
|
#[macro_export]
|
|
macro_rules! test_setup {
|
|
macro_rules! test_setup {
|
|
() => {
|
|
() => {
|
|
|
|
+ /// A tokio async runtime.
|
|
|
|
+ ///
|
|
|
|
+ /// When the `#[tokio::test]` attribute is used on a test, a new current thread runtime
|
|
|
|
+ /// is created for each test
|
|
|
|
+ /// (source: https://docs.rs/tokio/latest/tokio/attr.test.html#current-thread-runtime).
|
|
|
|
+ /// This creates a problem, because the first test thread to access the `RUNTIME` static
|
|
|
|
+ /// will initialize its `Receiver` in its runtime, which will stop running at the end of
|
|
|
|
+ /// the test. Hence subsequent tests will not be able to send remote messages to this
|
|
|
|
+ /// `Runtime`, nor will the kernel task be running.
|
|
|
|
+ ///
|
|
|
|
+ /// By creating a single async runtime which is used by all of the tests, we can avoid this
|
|
|
|
+ /// problem.
|
|
|
|
+ static ASYNC_RT: $crate::model::Lazy<::tokio::runtime::Runtime> =
|
|
|
|
+ $crate::model::Lazy::new(|| {
|
|
|
|
+ ::tokio::runtime::Builder::new_current_thread()
|
|
|
|
+ .enable_all()
|
|
|
|
+ .build()
|
|
|
|
+ .unwrap()
|
|
|
|
+ });
|
|
|
|
+
|
|
const RUNTIME_ADDR: ::std::net::IpAddr =
|
|
const RUNTIME_ADDR: ::std::net::IpAddr =
|
|
::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127, 0, 0, 1));
|
|
::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127, 0, 0, 1));
|
|
- lazy_static! {
|
|
|
|
- static ref RUNTIME_CREDS: ::std::sync::Arc<::btlib::crypto::ConcreteCreds> = {
|
|
|
|
- let test_store = &::btlib_tests::TEST_STORE;
|
|
|
|
- ::btlib::crypto::CredStore::node_creds(test_store).unwrap()
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
|
|
+ static RUNTIME_CREDS: $crate::model::Lazy<
|
|
|
|
+ ::std::sync::Arc<::btlib::crypto::ConcreteCreds>,
|
|
|
|
+ > = $crate::model::Lazy::new(|| {
|
|
|
|
+ let test_store = &::btlib_tests::TEST_STORE;
|
|
|
|
+ ::btlib::crypto::CredStore::node_creds(test_store).unwrap()
|
|
|
|
+ });
|
|
declare_runtime!(RUNTIME, RUNTIME_ADDR, RUNTIME_CREDS.clone());
|
|
declare_runtime!(RUNTIME, RUNTIME_ADDR, RUNTIME_CREDS.clone());
|
|
|
|
|
|
- lazy_static! {
|
|
|
|
- /// A tokio async runtime.
|
|
|
|
- ///
|
|
|
|
- /// When the `#[tokio::test]` attribute is used on a test, a new current thread runtime
|
|
|
|
- /// is created for each test
|
|
|
|
- /// (source: https://docs.rs/tokio/latest/tokio/attr.test.html#current-thread-runtime).
|
|
|
|
- /// This creates a problem, because the first test thread to access the `RUNTIME` static
|
|
|
|
- /// will initialize its `Receiver` in its runtime, which will stop running at the end of
|
|
|
|
- /// the test. Hence subsequent tests will not be able to send remote messages to this
|
|
|
|
- /// `Runtime`.
|
|
|
|
- ///
|
|
|
|
- /// By creating a single async runtime which is used by all of the tests, we can avoid this
|
|
|
|
- /// problem.
|
|
|
|
- static ref ASYNC_RT: tokio::runtime::Runtime = ::tokio::runtime::Builder
|
|
|
|
- ::new_current_thread()
|
|
|
|
- .enable_all()
|
|
|
|
- .build()
|
|
|
|
- .unwrap();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// The log level to use when running tests.
|
|
/// The log level to use when running tests.
|
|
const LOG_LEVEL: &str = "warn";
|
|
const LOG_LEVEL: &str = "warn";
|
|
|
|
|
|
#[::ctor::ctor]
|
|
#[::ctor::ctor]
|
|
- #[allow(non_snake_case)]
|
|
|
|
fn ctor() {
|
|
fn ctor() {
|
|
::std::env::set_var("RUST_LOG", format!("{},quinn=WARN", LOG_LEVEL));
|
|
::std::env::set_var("RUST_LOG", format!("{},quinn=WARN", LOG_LEVEL));
|
|
let mut builder = ::env_logger::Builder::from_default_env();
|
|
let mut builder = ::env_logger::Builder::from_default_env();
|
|
@@ -818,10 +727,9 @@ pub mod test {
|
|
use btlib::crypto::{CredStore, CredsPriv};
|
|
use btlib::crypto::{CredStore, CredsPriv};
|
|
use btlib_tests::TEST_STORE;
|
|
use btlib_tests::TEST_STORE;
|
|
use bttp::BlockAddr;
|
|
use bttp::BlockAddr;
|
|
- use lazy_static::lazy_static;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
- use crate::CallMsg;
|
|
|
|
|
|
+ use crate::{model::Lazy, CallMsg};
|
|
|
|
|
|
test_setup!();
|
|
test_setup!();
|
|
|
|
|
|
@@ -832,31 +740,53 @@ pub mod test {
|
|
type Reply = EchoMsg;
|
|
type Reply = EchoMsg;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ impl Named for EchoMsg {
|
|
|
|
+ fn name(&self) -> Arc<String> {
|
|
|
|
+ static NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("EchoMsg".into()));
|
|
|
|
+ NAME.clone()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
async fn echo(
|
|
async fn echo(
|
|
- _rt: &'static Runtime,
|
|
|
|
mut mailbox: mpsc::Receiver<Envelope<EchoMsg>>,
|
|
mut mailbox: mpsc::Receiver<Envelope<EchoMsg>>,
|
|
- _act_id: ActorId,
|
|
|
|
- ) {
|
|
|
|
|
|
+ actor_id: ActorId,
|
|
|
|
+ _rt: &'static Runtime,
|
|
|
|
+ ) -> ActorResult {
|
|
|
|
+ static ACTOR_IMPL: Lazy<Arc<String>> = Lazy::new(|| Arc::new("echo".into()));
|
|
|
|
+ static STATE_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("Listening".into()));
|
|
|
|
+ static MESSAGE_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("Ping".into()));
|
|
|
|
+
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
- let (msg, kind) = envelope.split();
|
|
|
|
- match kind {
|
|
|
|
- EnvelopeKind::Call { reply } => {
|
|
|
|
|
|
+ match envelope {
|
|
|
|
+ Envelope::Call { msg, reply, .. } => {
|
|
let replier =
|
|
let replier =
|
|
reply.unwrap_or_else(|| panic!("The reply has already been sent."));
|
|
reply.unwrap_or_else(|| panic!("The reply has already been sent."));
|
|
if let Err(_) = replier.send(msg) {
|
|
if let Err(_) = replier.send(msg) {
|
|
panic!("failed to send reply");
|
|
panic!("failed to send reply");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- _ => panic!("Expected EchoMsg to be a Call Message."),
|
|
|
|
|
|
+ _ => {
|
|
|
|
+ return Err(ActorError::new(
|
|
|
|
+ bterr!("Expected EchoMsg to be a Call Message."),
|
|
|
|
+ ActorErrorCommon {
|
|
|
|
+ actor_id,
|
|
|
|
+ actor_impl: ACTOR_IMPL.clone(),
|
|
|
|
+ state: STATE_NAME.clone(),
|
|
|
|
+ message: MESSAGE_NAME.clone(),
|
|
|
|
+ kind: TransKind::Receive,
|
|
|
|
+ },
|
|
|
|
+ ));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ Ok(actor_id)
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
fn local_call() {
|
|
fn local_call() {
|
|
ASYNC_RT.block_on(async {
|
|
ASYNC_RT.block_on(async {
|
|
const EXPECTED: &str = "hello";
|
|
const EXPECTED: &str = "hello";
|
|
- let name = RUNTIME.spawn(echo).await;
|
|
|
|
|
|
+ let name = RUNTIME.spawn(None, echo).await;
|
|
let from = ActorName::new(name.path().clone(), ActorId::new());
|
|
let from = ActorName::new(name.path().clone(), ActorId::new());
|
|
|
|
|
|
let reply = RUNTIME
|
|
let reply = RUNTIME
|
|
@@ -882,7 +812,7 @@ pub mod test {
|
|
TEST_STORE.node_creds().unwrap()
|
|
TEST_STORE.node_creds().unwrap()
|
|
);
|
|
);
|
|
assert_eq!(0, LOCAL_RT.num_running().await);
|
|
assert_eq!(0, LOCAL_RT.num_running().await);
|
|
- let name = LOCAL_RT.spawn(echo).await;
|
|
|
|
|
|
+ let name = LOCAL_RT.spawn(None, echo).await;
|
|
assert_eq!(1, LOCAL_RT.num_running().await);
|
|
assert_eq!(1, LOCAL_RT.num_running().await);
|
|
LOCAL_RT.take(&name).await.unwrap();
|
|
LOCAL_RT.take(&name).await.unwrap();
|
|
assert_eq!(0, LOCAL_RT.num_running().await);
|
|
assert_eq!(0, LOCAL_RT.num_running().await);
|
|
@@ -892,7 +822,7 @@ pub mod test {
|
|
fn remote_call() {
|
|
fn remote_call() {
|
|
ASYNC_RT.block_on(async {
|
|
ASYNC_RT.block_on(async {
|
|
const EXPECTED: &str = "hello";
|
|
const EXPECTED: &str = "hello";
|
|
- let actor_name = RUNTIME.spawn(echo).await;
|
|
|
|
|
|
+ let actor_name = RUNTIME.spawn(None, echo).await;
|
|
let bind_path = Arc::new(RUNTIME_CREDS.bind_path().unwrap());
|
|
let bind_path = Arc::new(RUNTIME_CREDS.bind_path().unwrap());
|
|
let block_addr = Arc::new(BlockAddr::new(RUNTIME_ADDR, bind_path));
|
|
let block_addr = Arc::new(BlockAddr::new(RUNTIME_ADDR, bind_path));
|
|
let transmitter = Transmitter::new(block_addr, RUNTIME_CREDS.clone())
|
|
let transmitter = Transmitter::new(block_addr, RUNTIME_CREDS.clone())
|