|
@@ -13,14 +13,18 @@ use std::{
|
|
};
|
|
};
|
|
|
|
|
|
use btlib::{bterr, crypto::Creds, error::StringError, BlockPath, Result};
|
|
use btlib::{bterr, crypto::Creds, error::StringError, BlockPath, Result};
|
|
-use btserde::{field_helpers::smart_ptr, from_slice, to_vec, write_to};
|
|
|
|
|
|
+use btserde::{from_slice, to_vec, write_to};
|
|
use bttp::{DeserCallback, MsgCallback, Receiver, Replier, Transmitter};
|
|
use bttp::{DeserCallback, MsgCallback, Receiver, Replier, Transmitter};
|
|
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
|
|
|
|
+use kernel::{kernel, SpawnReq};
|
|
|
|
+use serde::{Deserialize, Serialize};
|
|
use tokio::{
|
|
use tokio::{
|
|
sync::{mpsc, oneshot, Mutex, RwLock},
|
|
sync::{mpsc, oneshot, Mutex, RwLock},
|
|
- task::JoinHandle,
|
|
|
|
|
|
+ task::AbortHandle,
|
|
};
|
|
};
|
|
-use uuid::Uuid;
|
|
|
|
|
|
+
|
|
|
|
+mod kernel;
|
|
|
|
+pub mod model;
|
|
|
|
+use model::*;
|
|
|
|
|
|
/// Declares a new [Runtime] which listens for messages at the given IP address and uses the given
|
|
/// Declares a new [Runtime] which listens for messages at the given IP address and uses the given
|
|
/// [Creds]. Runtimes are intended to be created once in a process's lifetime and continue running
|
|
/// [Creds]. Runtimes are intended to be created once in a process's lifetime and continue running
|
|
@@ -29,9 +33,9 @@ use uuid::Uuid;
|
|
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! {
|
|
::lazy_static::lazy_static! {
|
|
- static ref $name: &'static ::btrun::Runtime = {
|
|
|
|
|
|
+ static ref $name: &'static $crate::Runtime = {
|
|
::lazy_static::lazy_static! {
|
|
::lazy_static::lazy_static! {
|
|
- static ref RUNTIME: ::btrun::Runtime = ::btrun::Runtime::_new($creds).unwrap();
|
|
|
|
|
|
+ static ref RUNTIME: $crate::Runtime = $crate::Runtime::_new($creds).unwrap();
|
|
static ref RECEIVER: ::bttp::Receiver = _new_receiver($ip_addr, $creds, &*RUNTIME);
|
|
static ref RECEIVER: ::bttp::Receiver = _new_receiver($ip_addr, $creds, &*RUNTIME);
|
|
}
|
|
}
|
|
// By dereferencing RECEIVER we ensure it is started.
|
|
// By dereferencing RECEIVER we ensure it is started.
|
|
@@ -63,12 +67,17 @@ pub type Mailbox<T> = mpsc::Receiver<Envelope<T>>;
|
|
/// be ready when the reply has been received.
|
|
/// be ready when the reply has been received.
|
|
pub struct Runtime {
|
|
pub struct Runtime {
|
|
path: Arc<BlockPath>,
|
|
path: Arc<BlockPath>,
|
|
- handles: RwLock<HashMap<Uuid, ActorHandle>>,
|
|
|
|
|
|
+ handles: RwLock<HashMap<ActorId, ActorHandle>>,
|
|
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>,
|
|
}
|
|
}
|
|
|
|
|
|
impl Runtime {
|
|
impl Runtime {
|
|
|
|
+ /// The size of the buffer to use for the channel between [Runtime] and [kernel] used for
|
|
|
|
+ /// spawning tasks.
|
|
|
|
+ const SPAWN_REQ_BUF_SZ: usize = 16;
|
|
|
|
+
|
|
/// This method is not intended to be called directly by downstream crates. Use the macro
|
|
/// This method is not intended to be called directly by downstream crates. Use the macro
|
|
/// [declare_runtime] to create a [Runtime].
|
|
/// [declare_runtime] to create a [Runtime].
|
|
///
|
|
///
|
|
@@ -76,11 +85,14 @@ impl Runtime {
|
|
#[doc(hidden)]
|
|
#[doc(hidden)]
|
|
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);
|
|
|
|
+ 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,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -101,16 +113,16 @@ impl Runtime {
|
|
from: ActorName,
|
|
from: ActorName,
|
|
msg: T,
|
|
msg: T,
|
|
) -> Result<()> {
|
|
) -> Result<()> {
|
|
- if to.path == self.path {
|
|
|
|
|
|
+ if to.path().as_ref() == self.path.as_ref() {
|
|
let guard = self.handles.read().await;
|
|
let guard = self.handles.read().await;
|
|
- 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(bterr!("invalid actor name"))
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
let guard = self.peers.read().await;
|
|
let guard = self.peers.read().await;
|
|
- if let Some(peer) = guard.get(&to.path) {
|
|
|
|
|
|
+ if let Some(peer) = guard.get(to.path()) {
|
|
let buf = to_vec(&msg)?;
|
|
let buf = to_vec(&msg)?;
|
|
let wire_msg = WireMsg {
|
|
let wire_msg = WireMsg {
|
|
to,
|
|
to,
|
|
@@ -119,9 +131,7 @@ impl Runtime {
|
|
};
|
|
};
|
|
peer.send(wire_msg).await
|
|
peer.send(wire_msg).await
|
|
} else {
|
|
} else {
|
|
- // TODO: Use the filesystem to discover the address of the recipient and connect to
|
|
|
|
- // it.
|
|
|
|
- todo!()
|
|
|
|
|
|
+ todo!("Discover the network location of the recipient runtime and connect to it.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -158,16 +168,16 @@ impl Runtime {
|
|
from: ActorName,
|
|
from: ActorName,
|
|
msg: T,
|
|
msg: T,
|
|
) -> Result<T::Reply> {
|
|
) -> Result<T::Reply> {
|
|
- if to.path == self.path {
|
|
|
|
|
|
+ if to.path().as_ref() == self.path.as_ref() {
|
|
let guard = self.handles.read().await;
|
|
let guard = self.handles.read().await;
|
|
- if let Some(handle) = guard.get(&to.act_id) {
|
|
|
|
|
|
+ if let Some(handle) = guard.get(&to.act_id()) {
|
|
handle.call_through(msg).await
|
|
handle.call_through(msg).await
|
|
} else {
|
|
} else {
|
|
Err(bterr!("invalid actor name"))
|
|
Err(bterr!("invalid actor name"))
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
let guard = self.peers.read().await;
|
|
let guard = self.peers.read().await;
|
|
- if let Some(peer) = guard.get(&to.path) {
|
|
|
|
|
|
+ if let Some(peer) = guard.get(to.path()) {
|
|
let buf = to_vec(&msg)?;
|
|
let buf = to_vec(&msg)?;
|
|
let wire_msg = WireMsg {
|
|
let wire_msg = WireMsg {
|
|
to,
|
|
to,
|
|
@@ -208,7 +218,7 @@ impl Runtime {
|
|
bterr!("Service is not registered: '{id}'")
|
|
bterr!("Service is not registered: '{id}'")
|
|
}
|
|
}
|
|
|
|
|
|
- async fn service_provider(&'static self, to: &ServiceAddr) -> Result<Uuid> {
|
|
|
|
|
|
+ async fn service_provider(&'static self, to: &ServiceAddr) -> Result<ActorId> {
|
|
let actor_id = {
|
|
let actor_id = {
|
|
let registry = self.registry.read().await;
|
|
let registry = self.registry.read().await;
|
|
if let Some(record) = registry.get(to.service_id()) {
|
|
if let Some(record) = registry.get(to.service_id()) {
|
|
@@ -227,7 +237,7 @@ impl Runtime {
|
|
if record.actor_ids.is_empty() {
|
|
if record.actor_ids.is_empty() {
|
|
let spawner = record.spawner.as_ref();
|
|
let spawner = record.spawner.as_ref();
|
|
let actor_name = spawner(self).await?;
|
|
let actor_name = spawner(self).await?;
|
|
- let actor_id = actor_name.act_id;
|
|
|
|
|
|
+ let actor_id = actor_name.act_id();
|
|
record.actor_ids.push(actor_id);
|
|
record.actor_ids.push(actor_id);
|
|
actor_id
|
|
actor_id
|
|
} else {
|
|
} else {
|
|
@@ -245,20 +255,20 @@ impl Runtime {
|
|
where
|
|
where
|
|
Msg: 'static + CallMsg,
|
|
Msg: 'static + CallMsg,
|
|
Fut: 'static + Send + Future<Output = ()>,
|
|
Fut: 'static + Send + Future<Output = ()>,
|
|
- F: FnOnce(&'static Runtime, Mailbox<Msg>, Uuid) -> Fut,
|
|
|
|
|
|
+ F: FnOnce(&'static Runtime, Mailbox<Msg>, ActorId) -> Fut,
|
|
{
|
|
{
|
|
let mut guard = self.handles.write().await;
|
|
let mut guard = self.handles.write().await;
|
|
let act_id = {
|
|
let act_id = {
|
|
- let mut act_id = Uuid::new_v4();
|
|
|
|
|
|
+ let mut act_id = ActorId::new();
|
|
while guard.contains_key(&act_id) {
|
|
while guard.contains_key(&act_id) {
|
|
- act_id = Uuid::new_v4();
|
|
|
|
|
|
+ act_id = ActorId::new();
|
|
}
|
|
}
|
|
act_id
|
|
act_id
|
|
};
|
|
};
|
|
let act_name = self.actor_name(act_id);
|
|
let act_name = self.actor_name(act_id);
|
|
let (tx, rx) = mpsc::channel::<Envelope<Msg>>(MAILBOX_LIMIT);
|
|
let (tx, rx) = mpsc::channel::<Envelope<Msg>>(MAILBOX_LIMIT);
|
|
// The deliverer closure is responsible for deserializing messages received over the wire
|
|
// The deliverer closure is responsible for deserializing messages received over the wire
|
|
- // and delivering them to the actor's mailbox, and sending replies to call messages.
|
|
|
|
|
|
+ // and delivering them to the actor's mailbox, as well as sending replies to call messages.
|
|
let deliverer = {
|
|
let deliverer = {
|
|
let buffer = Arc::new(Mutex::new(Vec::<u8>::new()));
|
|
let buffer = Arc::new(Mutex::new(Vec::<u8>::new()));
|
|
let tx = tx.clone();
|
|
let tx = tx.clone();
|
|
@@ -297,7 +307,14 @@ impl Runtime {
|
|
fut
|
|
fut
|
|
}
|
|
}
|
|
};
|
|
};
|
|
- let handle = tokio::task::spawn(activator(self, rx, act_id));
|
|
|
|
|
|
+ let (req, receiver) = SpawnReq::new(activator(self, rx, act_id));
|
|
|
|
+ self.kernel_sender
|
|
|
|
+ .send(req)
|
|
|
|
+ .await
|
|
|
|
+ .unwrap_or_else(|err| panic!("The kernel has panicked: {err}"));
|
|
|
|
+ let handle = receiver
|
|
|
|
+ .await
|
|
|
|
+ .unwrap_or_else(|err| panic!("Kernel failed to send abort handle: {err}"));
|
|
let actor_handle = ActorHandle::new(handle, tx, deliverer);
|
|
let actor_handle = ActorHandle::new(handle, tx, deliverer);
|
|
guard.insert(act_id, actor_handle);
|
|
guard.insert(act_id, actor_handle);
|
|
act_name
|
|
act_name
|
|
@@ -326,9 +343,40 @@ impl Runtime {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- pub async fn take_service(&self, id: &ServiceId) -> Option<ServiceRecord> {
|
|
|
|
- let mut registry = self.registry.write().await;
|
|
|
|
- registry.remove(id)
|
|
|
|
|
|
+ /// Removes the registration for the service with the given ID.
|
|
|
|
+ ///
|
|
|
|
+ /// If a vector reference is given in `service_providers`, the service providers which
|
|
|
|
+ /// are part of the deregistered service are appended to it. Otherwise, their
|
|
|
|
+ /// handles are dropped and their tasks are aborted.
|
|
|
|
+ ///
|
|
|
|
+ /// A [RuntimeError::BadServiceId] error is returned if there is no service registration with
|
|
|
|
+ /// the given ID in this runtime.
|
|
|
|
+ pub async fn deregister(
|
|
|
|
+ &self,
|
|
|
|
+ id: &ServiceId,
|
|
|
|
+ service_providers: Option<&mut Vec<ActorHandle>>,
|
|
|
|
+ ) -> Result<()> {
|
|
|
|
+ let record = {
|
|
|
|
+ let mut registry = self.registry.write().await;
|
|
|
|
+ if let Some(record) = registry.remove(id) {
|
|
|
|
+ record
|
|
|
|
+ } else {
|
|
|
|
+ return Err(RuntimeError::BadServiceId(id.clone()).into());
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ let mut handles = self.handles.write().await;
|
|
|
|
+ let removed = record
|
|
|
|
+ .actor_ids
|
|
|
|
+ .into_iter()
|
|
|
|
+ .flat_map(|act_id| handles.remove(&act_id));
|
|
|
|
+ // If a vector was provided, we put all the removed service providers in it. Otherwise
|
|
|
|
+ // we just drop them.
|
|
|
|
+ if let Some(service_providers) = service_providers {
|
|
|
|
+ service_providers.extend(removed);
|
|
|
|
+ } else {
|
|
|
|
+ for _ in removed {}
|
|
|
|
+ }
|
|
|
|
+ Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
/// Returns the [ActorHandle] for the actor with the given name.
|
|
/// Returns the [ActorHandle] for the actor with the given name.
|
|
@@ -340,9 +388,9 @@ impl Runtime {
|
|
/// returned when the handle is dropped), and no further messages will be delivered to it by
|
|
/// returned when the handle is dropped), and no further messages will be delivered to it by
|
|
/// this runtime.
|
|
/// this runtime.
|
|
pub async fn take(&self, name: &ActorName) -> Result<ActorHandle> {
|
|
pub async fn take(&self, name: &ActorName) -> Result<ActorHandle> {
|
|
- if name.path == self.path {
|
|
|
|
|
|
+ if name.path().as_ref() == self.path.as_ref() {
|
|
let mut guard = self.handles.write().await;
|
|
let mut guard = self.handles.write().await;
|
|
- if let Some(handle) = guard.remove(&name.act_id) {
|
|
|
|
|
|
+ if let Some(handle) = guard.remove(&name.act_id()) {
|
|
Ok(handle)
|
|
Ok(handle)
|
|
} else {
|
|
} else {
|
|
Err(RuntimeError::BadActorName(name.clone()).into())
|
|
Err(RuntimeError::BadActorName(name.clone()).into())
|
|
@@ -353,7 +401,7 @@ impl Runtime {
|
|
}
|
|
}
|
|
|
|
|
|
/// Returns the name of the actor in this runtime with the given actor ID.
|
|
/// Returns the name of the actor in this runtime with the given actor ID.
|
|
- pub fn actor_name(&self, act_id: Uuid) -> ActorName {
|
|
|
|
|
|
+ pub fn actor_name(&self, act_id: ActorId) -> ActorName {
|
|
ActorName::new(self.path.clone(), act_id)
|
|
ActorName::new(self.path.clone(), act_id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -364,28 +412,13 @@ impl Drop for Runtime {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
-pub enum RuntimeError {
|
|
|
|
- BadActorName(ActorName),
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Display for RuntimeError {
|
|
|
|
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
- match self {
|
|
|
|
- Self::BadActorName(name) => write!(f, "bad actor name: {name}"),
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl std::error::Error for RuntimeError {}
|
|
|
|
-
|
|
|
|
/// Closure type used to spawn new service providers.
|
|
/// Closure type used to spawn new service providers.
|
|
-pub type Spawner =
|
|
|
|
|
|
+type Spawner =
|
|
Box<dyn Send + Sync + Fn(&'static Runtime) -> Pin<Box<dyn Future<Output = Result<ActorName>>>>>;
|
|
Box<dyn Send + Sync + Fn(&'static Runtime) -> Pin<Box<dyn Future<Output = Result<ActorName>>>>>;
|
|
|
|
|
|
-pub struct ServiceRecord {
|
|
|
|
|
|
+struct ServiceRecord {
|
|
spawner: Spawner,
|
|
spawner: Spawner,
|
|
- actor_ids: Vec<Uuid>,
|
|
|
|
|
|
+ actor_ids: Vec<ActorId>,
|
|
}
|
|
}
|
|
|
|
|
|
impl ServiceRecord {
|
|
impl ServiceRecord {
|
|
@@ -403,38 +436,32 @@ impl ServiceRecord {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/// Represents the terminal state of an actor, where it stops processing messages and halts.
|
|
|
|
-pub struct End;
|
|
|
|
-
|
|
|
|
-impl End {
|
|
|
|
- /// Returns the identifier for this type which is expected in protocol definitions.
|
|
|
|
- pub fn ident() -> &'static str {
|
|
|
|
- stringify!(End)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[allow(dead_code)]
|
|
|
|
-/// Delivered to an actor implementation when it starts up.
|
|
|
|
-pub struct Activate {
|
|
|
|
- /// A reference to the `Runtime` which is running this actor.
|
|
|
|
- rt: &'static Runtime,
|
|
|
|
- /// The ID assigned to this actor.
|
|
|
|
- act_id: Uuid,
|
|
|
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
+pub enum RuntimeError {
|
|
|
|
+ BadActorName(ActorName),
|
|
|
|
+ BadServiceId(ServiceId),
|
|
}
|
|
}
|
|
|
|
|
|
-impl Activate {
|
|
|
|
- pub fn new(rt: &'static Runtime, act_id: Uuid) -> Self {
|
|
|
|
- Self { rt, act_id }
|
|
|
|
|
|
+impl Display for RuntimeError {
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
+ match self {
|
|
|
|
+ Self::BadActorName(name) => write!(f, "bad actor name: {name}"),
|
|
|
|
+ Self::BadServiceId(service_id) => {
|
|
|
|
+ write!(f, "service ID is not registered: {service_id}")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+impl std::error::Error for RuntimeError {}
|
|
|
|
+
|
|
/// Deserializes replies sent over the wire.
|
|
/// Deserializes replies sent over the wire.
|
|
-pub struct ReplyCallback<T> {
|
|
|
|
|
|
+struct ReplyCallback<T> {
|
|
_phantom: PhantomData<T>,
|
|
_phantom: PhantomData<T>,
|
|
}
|
|
}
|
|
|
|
|
|
impl<T: CallMsg> ReplyCallback<T> {
|
|
impl<T: CallMsg> ReplyCallback<T> {
|
|
- pub fn new() -> Self {
|
|
|
|
|
|
+ fn new() -> Self {
|
|
Self {
|
|
Self {
|
|
_phantom: PhantomData,
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
@@ -500,7 +527,7 @@ impl RuntimeCallback {
|
|
|
|
|
|
async fn deliver_local(&self, msg: WireMsg<'_>, replier: Option<Replier>) -> Result<()> {
|
|
async fn deliver_local(&self, msg: WireMsg<'_>, replier: Option<Replier>) -> Result<()> {
|
|
let guard = self.rt.handles.read().await;
|
|
let guard = self.rt.handles.read().await;
|
|
- if let Some(handle) = guard.get(&msg.to.act_id) {
|
|
|
|
|
|
+ if let Some(handle) = guard.get(&msg.to.act_id()) {
|
|
let envelope = if let Some(replier) = replier {
|
|
let envelope = if let Some(replier) = replier {
|
|
WireEnvelope::Call { msg, replier }
|
|
WireEnvelope::Call { msg, replier }
|
|
} else {
|
|
} else {
|
|
@@ -545,135 +572,20 @@ impl MsgCallback for RuntimeCallback {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/// A unique identifier for a particular service.
|
|
|
|
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
|
|
|
-pub struct ServiceId(#[serde(with = "smart_ptr")] Arc<String>);
|
|
|
|
-
|
|
|
|
-impl ServiceId {
|
|
|
|
- pub fn new(value: Arc<String>) -> Self {
|
|
|
|
- Self(value)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl AsRef<str> for ServiceId {
|
|
|
|
- fn as_ref(&self) -> &str {
|
|
|
|
- self.0.as_str()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<T: Into<String>> From<T> for ServiceId {
|
|
|
|
- fn from(value: T) -> Self {
|
|
|
|
- Self(Arc::new(value.into()))
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Display for ServiceId {
|
|
|
|
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
- f.write_str(self.as_ref())
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// A unique identifier for a service.
|
|
|
|
-///
|
|
|
|
-/// A service is a collection of actors in the same directory which provide some functionality.
|
|
|
|
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
|
|
|
-pub struct ServiceName {
|
|
|
|
- /// The path to the directory containing the service.
|
|
|
|
- #[serde(with = "smart_ptr")]
|
|
|
|
- path: Arc<BlockPath>,
|
|
|
|
- /// The id of the service.
|
|
|
|
- service_id: ServiceId,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl ServiceName {
|
|
|
|
- pub fn new(path: Arc<BlockPath>, service_id: ServiceId) -> Self {
|
|
|
|
- Self { path, service_id }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Indicates the set of service providers a message is addressed to.
|
|
|
|
-///
|
|
|
|
-/// The message could be delivered to any of the service providers in this set, at random.
|
|
|
|
-pub struct ServiceAddr {
|
|
|
|
- /// The [ServiceName] to address the message to.
|
|
|
|
- name: ServiceName,
|
|
|
|
- #[allow(dead_code)]
|
|
|
|
- /// Indicates if the message should be routed towards the root of the tree or away from it.
|
|
|
|
- rootward: bool,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl ServiceAddr {
|
|
|
|
- pub fn new(name: ServiceName, rootward: bool) -> Self {
|
|
|
|
- Self { name, rootward }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn path(&self) -> &Arc<BlockPath> {
|
|
|
|
- &self.name.path
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn service_id(&self) -> &ServiceId {
|
|
|
|
- &self.name.service_id
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// A unique identifier for a specific actor activation.
|
|
|
|
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
|
|
|
-pub struct ActorName {
|
|
|
|
- /// The path to the directory containing this actor.
|
|
|
|
- #[serde(with = "smart_ptr")]
|
|
|
|
- path: Arc<BlockPath>,
|
|
|
|
- /// A unique identifier for an actor activation. Even as an actor transitions to different types
|
|
|
|
- /// as it handles messages, this value does not change. Thus this value can be used to trace an
|
|
|
|
- /// actor through a series of state transitions.
|
|
|
|
- act_id: Uuid,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl ActorName {
|
|
|
|
- pub fn new(path: Arc<BlockPath>, act_id: Uuid) -> Self {
|
|
|
|
- Self { path, act_id }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn path(&self) -> &Arc<BlockPath> {
|
|
|
|
- &self.path
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn act_id(&self) -> Uuid {
|
|
|
|
- self.act_id
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl Display for ActorName {
|
|
|
|
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
- write!(f, "{}@{}", self.act_id, self.path)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Trait for messages which expect exactly one reply.
|
|
|
|
-pub trait CallMsg: Serialize + DeserializeOwned + Send + Sync {
|
|
|
|
- /// The reply type expected for this message.
|
|
|
|
- type Reply: Serialize + DeserializeOwned + Send + Sync;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Trait for messages which expect exactly zero replies.
|
|
|
|
-pub trait SendMsg: CallMsg {}
|
|
|
|
-
|
|
|
|
-/// A type used to express when a reply is not expected for a message type.
|
|
|
|
-#[derive(Serialize, Deserialize)]
|
|
|
|
-pub enum NoReply {}
|
|
|
|
-
|
|
|
|
/// The maximum number of messages which can be kept in an actor's mailbox.
|
|
/// The maximum number of messages which can be kept in an actor's mailbox.
|
|
const MAILBOX_LIMIT: usize = 32;
|
|
const MAILBOX_LIMIT: usize = 32;
|
|
|
|
|
|
/// The type of messages sent over the wire between runtimes.
|
|
/// The type of messages sent over the wire between runtimes.
|
|
#[derive(Serialize, Deserialize)]
|
|
#[derive(Serialize, Deserialize)]
|
|
-pub struct WireMsg<'a> {
|
|
|
|
|
|
+struct WireMsg<'a> {
|
|
to: ActorName,
|
|
to: ActorName,
|
|
from: ActorName,
|
|
from: ActorName,
|
|
payload: &'a [u8],
|
|
payload: &'a [u8],
|
|
}
|
|
}
|
|
|
|
|
|
impl<'a> WireMsg<'a> {
|
|
impl<'a> WireMsg<'a> {
|
|
- pub fn new(to: ActorName, from: ActorName, payload: &'a [u8]) -> Self {
|
|
|
|
|
|
+ #[allow(dead_code)]
|
|
|
|
+ fn new(to: ActorName, from: ActorName, payload: &'a [u8]) -> Self {
|
|
Self { to, from, payload }
|
|
Self { to, from, payload }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -685,7 +597,7 @@ impl<'a> bttp::CallMsg<'a> for WireMsg<'a> {
|
|
impl<'a> bttp::SendMsg<'a> for WireMsg<'a> {}
|
|
impl<'a> bttp::SendMsg<'a> for WireMsg<'a> {}
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[derive(Serialize, Deserialize)]
|
|
-pub enum WireReply<'a> {
|
|
|
|
|
|
+enum WireReply<'a> {
|
|
Ok(&'a [u8]),
|
|
Ok(&'a [u8]),
|
|
Err(&'a str),
|
|
Err(&'a str),
|
|
}
|
|
}
|
|
@@ -796,19 +708,19 @@ impl<T: CallMsg> Envelope<T> {
|
|
type FutureResult = Pin<Box<dyn Send + Future<Output = Result<()>>>>;
|
|
type FutureResult = Pin<Box<dyn Send + Future<Output = Result<()>>>>;
|
|
|
|
|
|
pub struct ActorHandle {
|
|
pub struct ActorHandle {
|
|
- handle: Option<JoinHandle<()>>,
|
|
|
|
|
|
+ handle: AbortHandle,
|
|
sender: Box<dyn Send + Sync + Any>,
|
|
sender: Box<dyn Send + Sync + Any>,
|
|
deliverer: Box<dyn Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult>,
|
|
deliverer: Box<dyn Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult>,
|
|
}
|
|
}
|
|
|
|
|
|
impl ActorHandle {
|
|
impl ActorHandle {
|
|
- fn new<T, F>(handle: JoinHandle<()>, sender: mpsc::Sender<Envelope<T>>, deliverer: F) -> Self
|
|
|
|
|
|
+ fn new<T, F>(handle: AbortHandle, sender: mpsc::Sender<Envelope<T>>, deliverer: F) -> Self
|
|
where
|
|
where
|
|
T: 'static + CallMsg,
|
|
T: 'static + CallMsg,
|
|
F: 'static + Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult,
|
|
F: 'static + Send + Sync + Fn(WireEnvelope<'_>) -> FutureResult,
|
|
{
|
|
{
|
|
Self {
|
|
Self {
|
|
- handle: Some(handle),
|
|
|
|
|
|
+ handle,
|
|
sender: Box::new(sender),
|
|
sender: Box::new(sender),
|
|
deliverer: Box::new(deliverer),
|
|
deliverer: Box::new(deliverer),
|
|
}
|
|
}
|
|
@@ -841,17 +753,8 @@ impl ActorHandle {
|
|
Ok(reply)
|
|
Ok(reply)
|
|
}
|
|
}
|
|
|
|
|
|
- pub async fn returned(&mut self) -> Result<()> {
|
|
|
|
- if let Some(handle) = self.handle.take() {
|
|
|
|
- handle.await?;
|
|
|
|
- }
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- pub fn abort(&mut self) {
|
|
|
|
- if let Some(handle) = self.handle.take() {
|
|
|
|
- handle.abort();
|
|
|
|
- }
|
|
|
|
|
|
+ pub fn abort(&self) {
|
|
|
|
+ self.handle.abort();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -860,3 +763,154 @@ impl Drop for ActorHandle {
|
|
self.abort();
|
|
self.abort();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+/// Sets up variable declarations and logging configuration to facilitate testing with a [Runtime].
|
|
|
|
+#[macro_export]
|
|
|
|
+macro_rules! test_setup {
|
|
|
|
+ () => {
|
|
|
|
+ const RUNTIME_ADDR: ::std::net::IpAddr =
|
|
|
|
+ ::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()
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ 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.
|
|
|
|
+ const LOG_LEVEL: &str = "warn";
|
|
|
|
+
|
|
|
|
+ #[::ctor::ctor]
|
|
|
|
+ #[allow(non_snake_case)]
|
|
|
|
+ fn ctor() {
|
|
|
|
+ ::std::env::set_var("RUST_LOG", format!("{},quinn=WARN", LOG_LEVEL));
|
|
|
|
+ let mut builder = ::env_logger::Builder::from_default_env();
|
|
|
|
+ ::btlib::log::BuilderExt::btformat(&mut builder).init();
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[cfg(test)]
|
|
|
|
+pub mod test {
|
|
|
|
+ use super::*;
|
|
|
|
+
|
|
|
|
+ use btlib::crypto::{CredStore, CredsPriv};
|
|
|
|
+ use btlib_tests::TEST_STORE;
|
|
|
|
+ use bttp::BlockAddr;
|
|
|
|
+ use lazy_static::lazy_static;
|
|
|
|
+ use serde::{Deserialize, Serialize};
|
|
|
|
+
|
|
|
|
+ use crate::CallMsg;
|
|
|
|
+
|
|
|
|
+ test_setup!();
|
|
|
|
+
|
|
|
|
+ #[derive(Serialize, Deserialize)]
|
|
|
|
+ struct EchoMsg(String);
|
|
|
|
+
|
|
|
|
+ impl CallMsg for EchoMsg {
|
|
|
|
+ type Reply = EchoMsg;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async fn echo(
|
|
|
|
+ _rt: &'static Runtime,
|
|
|
|
+ mut mailbox: mpsc::Receiver<Envelope<EchoMsg>>,
|
|
|
|
+ _act_id: ActorId,
|
|
|
|
+ ) {
|
|
|
|
+ while let Some(envelope) = mailbox.recv().await {
|
|
|
|
+ let (msg, kind) = envelope.split();
|
|
|
|
+ match kind {
|
|
|
|
+ EnvelopeKind::Call { reply } => {
|
|
|
|
+ let replier =
|
|
|
|
+ reply.unwrap_or_else(|| panic!("The reply has already been sent."));
|
|
|
|
+ if let Err(_) = replier.send(msg) {
|
|
|
|
+ panic!("failed to send reply");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ _ => panic!("Expected EchoMsg to be a Call Message."),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn local_call() {
|
|
|
|
+ ASYNC_RT.block_on(async {
|
|
|
|
+ const EXPECTED: &str = "hello";
|
|
|
|
+ let name = RUNTIME.spawn(echo).await;
|
|
|
|
+ let from = ActorName::new(name.path().clone(), ActorId::new());
|
|
|
|
+
|
|
|
|
+ let reply = RUNTIME
|
|
|
|
+ .call(name.clone(), from, EchoMsg(EXPECTED.into()))
|
|
|
|
+ .await
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, reply.0);
|
|
|
|
+
|
|
|
|
+ RUNTIME.take(&name).await.unwrap();
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Tests the `num_running` method.
|
|
|
|
+ ///
|
|
|
|
+ /// This test uses its own runtime and so can use the `#[tokio::test]` attribute.
|
|
|
|
+ #[tokio::test]
|
|
|
|
+ async fn num_running() {
|
|
|
|
+ declare_runtime!(
|
|
|
|
+ LOCAL_RT,
|
|
|
|
+ // This needs to be different from the address where `RUNTIME` is listening.
|
|
|
|
+ IpAddr::from([127, 0, 0, 2]),
|
|
|
|
+ TEST_STORE.node_creds().unwrap()
|
|
|
|
+ );
|
|
|
|
+ assert_eq!(0, LOCAL_RT.num_running().await);
|
|
|
|
+ let name = LOCAL_RT.spawn(echo).await;
|
|
|
|
+ assert_eq!(1, LOCAL_RT.num_running().await);
|
|
|
|
+ LOCAL_RT.take(&name).await.unwrap();
|
|
|
|
+ assert_eq!(0, LOCAL_RT.num_running().await);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn remote_call() {
|
|
|
|
+ ASYNC_RT.block_on(async {
|
|
|
|
+ const EXPECTED: &str = "hello";
|
|
|
|
+ let actor_name = RUNTIME.spawn(echo).await;
|
|
|
|
+ let bind_path = Arc::new(RUNTIME_CREDS.bind_path().unwrap());
|
|
|
|
+ let block_addr = Arc::new(BlockAddr::new(RUNTIME_ADDR, bind_path));
|
|
|
|
+ let transmitter = Transmitter::new(block_addr, RUNTIME_CREDS.clone())
|
|
|
|
+ .await
|
|
|
|
+ .unwrap();
|
|
|
|
+ let buf = to_vec(&EchoMsg(EXPECTED.to_string())).unwrap();
|
|
|
|
+ let wire_msg =
|
|
|
|
+ WireMsg::new(actor_name.clone(), RUNTIME.actor_name(ActorId::new()), &buf);
|
|
|
|
+
|
|
|
|
+ let reply = transmitter
|
|
|
|
+ .call(wire_msg, ReplyCallback::<EchoMsg>::new())
|
|
|
|
+ .await
|
|
|
|
+ .unwrap()
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ assert_eq!(EXPECTED, reply.0);
|
|
|
|
+
|
|
|
|
+ RUNTIME.take(&actor_name).await.unwrap();
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+}
|