|
@@ -22,7 +22,6 @@ use std::{
|
|
atomic::{AtomicU8, Ordering},
|
|
atomic::{AtomicU8, Ordering},
|
|
Arc,
|
|
Arc,
|
|
},
|
|
},
|
|
- time::{Duration, Instant},
|
|
|
|
};
|
|
};
|
|
use tokio::{runtime::Builder, sync::mpsc};
|
|
use tokio::{runtime::Builder, sync::mpsc};
|
|
use uuid::Uuid;
|
|
use uuid::Uuid;
|
|
@@ -74,11 +73,15 @@ async fn echo(
|
|
_act_id: Uuid,
|
|
_act_id: Uuid,
|
|
) {
|
|
) {
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
- let (msg, replier, ..) = envelope.split();
|
|
|
|
- if let Some(replier) = replier {
|
|
|
|
- if let Err(_) = replier.send(msg) {
|
|
|
|
- panic!("failed to send reply");
|
|
|
|
|
|
+ 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."),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -87,7 +90,7 @@ async fn echo(
|
|
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.activate(echo).await;
|
|
|
|
|
|
+ let name = RUNTIME.spawn(echo).await;
|
|
let from = ActorName::new(name.path().clone(), Uuid::default());
|
|
let from = ActorName::new(name.path().clone(), Uuid::default());
|
|
|
|
|
|
let reply = RUNTIME
|
|
let reply = RUNTIME
|
|
@@ -105,7 +108,7 @@ fn local_call() {
|
|
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.activate(echo).await;
|
|
|
|
|
|
+ let actor_name = RUNTIME.spawn(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())
|
|
@@ -142,7 +145,7 @@ async fn num_running() {
|
|
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.activate(echo).await;
|
|
|
|
|
|
+ let name = LOCAL_RT.spawn(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);
|
|
@@ -151,10 +154,12 @@ async fn num_running() {
|
|
mod ping_pong {
|
|
mod ping_pong {
|
|
use super::*;
|
|
use super::*;
|
|
|
|
|
|
|
|
+ use btlib::bterr;
|
|
|
|
+
|
|
// The following code is a proof-of-concept for what types should be generated for a
|
|
// The following code is a proof-of-concept for what types should be generated for a
|
|
// simple ping-pong protocol:
|
|
// simple ping-pong protocol:
|
|
protocol! {
|
|
protocol! {
|
|
- named PingPongProtocol;
|
|
|
|
|
|
+ named PingProtocol;
|
|
let server = [Server];
|
|
let server = [Server];
|
|
let client = [Client];
|
|
let client = [Client];
|
|
Client -> End, >service(Server)!Ping;
|
|
Client -> End, >service(Server)!Ping;
|
|
@@ -175,192 +180,197 @@ mod ping_pong {
|
|
// When a state is expecting a Reply message, an error occurs if the message is not received
|
|
// When a state is expecting a Reply message, an error occurs if the message is not received
|
|
// in a timely manner.
|
|
// in a timely manner.
|
|
|
|
|
|
- #[derive(Serialize, Deserialize)]
|
|
|
|
- pub struct Ping;
|
|
|
|
- impl CallMsg for Ping {
|
|
|
|
- type Reply = PingReply;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // I was tempted to name this "Pong", but the proc macro wouldn't think to do that.
|
|
|
|
- #[derive(Serialize, Deserialize)]
|
|
|
|
- pub struct PingReply;
|
|
|
|
-
|
|
|
|
- trait ClientInit2 {
|
|
|
|
- type AfterActivate: SentPing2;
|
|
|
|
- type HandleActivateFut: Future<Output = Result<(Self::AfterActivate, Ping)>>;
|
|
|
|
- fn handle_activate(self, msg: Activate) -> Self::HandleActivateFut;
|
|
|
|
|
|
+ enum PingClientState<T: Client> {
|
|
|
|
+ Client(T),
|
|
|
|
+ End(End),
|
|
}
|
|
}
|
|
|
|
|
|
- trait ServerInit2 {
|
|
|
|
- type AfterActivate: Listening2;
|
|
|
|
- type HandleActivateFut: Future<Output = Result<Self::AfterActivate>>;
|
|
|
|
- fn handle_activate(self, msg: Activate) -> Self::HandleActivateFut;
|
|
|
|
|
|
+ impl<T: Client> PingClientState<T> {
|
|
|
|
+ const fn name(&self) -> &'static str {
|
|
|
|
+ match self {
|
|
|
|
+ Self::Client(_) => "Client",
|
|
|
|
+ Self::End(_) => "End",
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- trait Listening2 {
|
|
|
|
- type HandlePingFut: Future<Output = Result<(End, PingReply)>>;
|
|
|
|
- fn handle_ping(self, msg: Ping) -> Self::HandlePingFut;
|
|
|
|
|
|
+ struct ClientHandle<T: Client> {
|
|
|
|
+ state: Option<PingClientState<T>>,
|
|
|
|
+ runtime: &'static Runtime,
|
|
}
|
|
}
|
|
|
|
|
|
- trait SentPing2 {
|
|
|
|
- type HandleReplyFut: Future<Output = Result<End>>;
|
|
|
|
- fn handle_reply(self, msg: PingReply) -> Self::HandleReplyFut;
|
|
|
|
|
|
+ impl<T: Client> ClientHandle<T> {
|
|
|
|
+ async fn send_ping(&mut self, mut msg: Ping, service: ServiceAddr) -> Result<PingReply> {
|
|
|
|
+ let state = self
|
|
|
|
+ .state
|
|
|
|
+ .take()
|
|
|
|
+ .ok_or_else(|| bterr!("State was not returned."))?;
|
|
|
|
+ let (new_state, result) = match state {
|
|
|
|
+ PingClientState::Client(state) => {
|
|
|
|
+ let (new_state, _) = state.on_send_ping(&mut msg).await?;
|
|
|
|
+ let new_state = PingClientState::End(new_state);
|
|
|
|
+ let result = self
|
|
|
|
+ .runtime
|
|
|
|
+ .call_service(service, PingProtocolMsgs::Ping(msg))
|
|
|
|
+ .await;
|
|
|
|
+ (new_state, result)
|
|
|
|
+ }
|
|
|
|
+ state => {
|
|
|
|
+ let result = Err(bterr!("Can't send Ping in state {}.", state.name()));
|
|
|
|
+ (state, result)
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ self.state = Some(new_state);
|
|
|
|
+ let reply = result?;
|
|
|
|
+ match reply {
|
|
|
|
+ PingProtocolMsgs::PingReply(reply) => Ok(reply),
|
|
|
|
+ msg => Err(bterr!(
|
|
|
|
+ "Unexpected message type sent in reply: {}",
|
|
|
|
+ msg.name()
|
|
|
|
+ )),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- #[derive(Serialize, Deserialize)]
|
|
|
|
- enum PingProtocolMsg {
|
|
|
|
- Ping(Ping),
|
|
|
|
- PingReply(PingReply),
|
|
|
|
- }
|
|
|
|
- impl CallMsg for PingProtocolMsg {
|
|
|
|
- type Reply = PingProtocolMsg;
|
|
|
|
|
|
+ async fn spawn_client<T: Client>(init: T, runtime: &'static Runtime) -> ClientHandle<T> {
|
|
|
|
+ let state = Some(PingClientState::Client(init));
|
|
|
|
+ ClientHandle { state, runtime }
|
|
}
|
|
}
|
|
- impl SendMsg for PingProtocolMsg {}
|
|
|
|
|
|
|
|
- struct ClientInitState;
|
|
|
|
-
|
|
|
|
- impl ClientInit2 for ClientInitState {
|
|
|
|
- type AfterActivate = ClientState;
|
|
|
|
- type HandleActivateFut = impl Future<Output = Result<(Self::AfterActivate, Ping)>>;
|
|
|
|
- fn handle_activate(self, _msg: Activate) -> Self::HandleActivateFut {
|
|
|
|
- ready(Ok((ClientState, Ping)))
|
|
|
|
|
|
+ async fn register_server<Init, F>(
|
|
|
|
+ make_init: F,
|
|
|
|
+ rt: &'static Runtime,
|
|
|
|
+ id: ServiceId,
|
|
|
|
+ ) -> Result<ServiceName>
|
|
|
|
+ where
|
|
|
|
+ Init: 'static + Server,
|
|
|
|
+ F: 'static + Send + Sync + Clone + Fn() -> Init,
|
|
|
|
+ {
|
|
|
|
+ enum ServerState<S> {
|
|
|
|
+ Server(S),
|
|
|
|
+ End(End),
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- struct ClientState;
|
|
|
|
|
|
+ async fn server_loop<Init, F>(
|
|
|
|
+ _runtime: &'static Runtime,
|
|
|
|
+ make_init: F,
|
|
|
|
+ mut mailbox: Mailbox<PingProtocolMsgs>,
|
|
|
|
+ _act_id: Uuid,
|
|
|
|
+ ) where
|
|
|
|
+ Init: 'static + Server,
|
|
|
|
+ F: 'static + Send + Sync + FnOnce() -> Init,
|
|
|
|
+ {
|
|
|
|
+ let mut state = ServerState::Server(make_init());
|
|
|
|
+ while let Some(envelope) = mailbox.recv().await {
|
|
|
|
+ let (msg, msg_kind) = envelope.split();
|
|
|
|
+ state = match (state, msg) {
|
|
|
|
+ (ServerState::Server(listening_state), PingProtocolMsgs::Ping(msg)) => {
|
|
|
|
+ let (new_state, reply) = listening_state.handle_ping(msg).await.unwrap();
|
|
|
|
+ match msg_kind {
|
|
|
|
+ EnvelopeKind::Call { reply: replier } => {
|
|
|
|
+ let replier = replier.expect("The reply has already been sent.");
|
|
|
|
+ if let Err(_) = replier.send(PingProtocolMsgs::PingReply(reply)) {
|
|
|
|
+ panic!("Failed to send Ping reply.");
|
|
|
|
+ }
|
|
|
|
+ ServerState::End(new_state)
|
|
|
|
+ }
|
|
|
|
+ _ => panic!("'Ping' was expected to be a Call message."),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ (state, _) => state,
|
|
|
|
+ };
|
|
|
|
|
|
- impl SentPing2 for ClientState {
|
|
|
|
- type HandleReplyFut = Ready<Result<End>>;
|
|
|
|
- fn handle_reply(self, _msg: PingReply) -> Self::HandleReplyFut {
|
|
|
|
- ready(Ok(End))
|
|
|
|
|
|
+ if let ServerState::End(_) = state {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ rt.register::<PingProtocolMsgs, _>(id, move |runtime| {
|
|
|
|
+ let make_init = make_init.clone();
|
|
|
|
+ let fut = async move {
|
|
|
|
+ let actor_name = runtime
|
|
|
|
+ .spawn(move |_, mailbox, act_id| {
|
|
|
|
+ server_loop(runtime, make_init, mailbox, act_id)
|
|
|
|
+ })
|
|
|
|
+ .await;
|
|
|
|
+ Ok(actor_name)
|
|
|
|
+ };
|
|
|
|
+ Box::pin(fut)
|
|
|
|
+ })
|
|
|
|
+ .await
|
|
}
|
|
}
|
|
|
|
|
|
- #[allow(dead_code)]
|
|
|
|
- enum PingClientState {
|
|
|
|
- Init(ClientInitState),
|
|
|
|
- SentPing(ClientState),
|
|
|
|
- End(End),
|
|
|
|
|
|
+ #[derive(Serialize, Deserialize)]
|
|
|
|
+ pub struct Ping;
|
|
|
|
+ impl CallMsg for Ping {
|
|
|
|
+ type Reply = PingReply;
|
|
}
|
|
}
|
|
|
|
|
|
- struct ServerInitState;
|
|
|
|
|
|
+ #[derive(Serialize, Deserialize)]
|
|
|
|
+ pub struct PingReply;
|
|
|
|
|
|
- struct ServerState;
|
|
|
|
|
|
+ struct ClientState {
|
|
|
|
+ counter: Arc<AtomicU8>,
|
|
|
|
+ }
|
|
|
|
|
|
- impl ServerInit2 for ServerInitState {
|
|
|
|
- type AfterActivate = ServerState;
|
|
|
|
- type HandleActivateFut = Ready<Result<Self::AfterActivate>>;
|
|
|
|
- fn handle_activate(self, _msg: Activate) -> Self::HandleActivateFut {
|
|
|
|
- ready(Ok(ServerState))
|
|
|
|
|
|
+ impl ClientState {
|
|
|
|
+ fn new(counter: Arc<AtomicU8>) -> Self {
|
|
|
|
+ counter.fetch_add(1, Ordering::SeqCst);
|
|
|
|
+ Self { counter }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- impl Listening2 for ServerState {
|
|
|
|
- type HandlePingFut = impl Future<Output = Result<(End, PingReply)>>;
|
|
|
|
- fn handle_ping(self, _msg: Ping) -> Self::HandlePingFut {
|
|
|
|
|
|
+ impl Client for ClientState {
|
|
|
|
+ type OnSendPingFut = impl Future<Output = Result<(End, PingReply)>>;
|
|
|
|
+ fn on_send_ping(self, _msg: &mut Ping) -> Self::OnSendPingFut {
|
|
|
|
+ self.counter.fetch_sub(1, Ordering::SeqCst);
|
|
ready(Ok((End, PingReply)))
|
|
ready(Ok((End, PingReply)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- #[allow(dead_code)]
|
|
|
|
- enum PingServerState {
|
|
|
|
- ServerInit(ServerInitState),
|
|
|
|
- Listening(ServerState),
|
|
|
|
- End(End),
|
|
|
|
|
|
+ struct ServerState {
|
|
|
|
+ counter: Arc<AtomicU8>,
|
|
}
|
|
}
|
|
|
|
|
|
- async fn ping_server(
|
|
|
|
- counter: Arc<AtomicU8>,
|
|
|
|
- rt: &'static Runtime,
|
|
|
|
- mut mailbox: mpsc::Receiver<Envelope<PingProtocolMsg>>,
|
|
|
|
- act_id: Uuid,
|
|
|
|
- ) {
|
|
|
|
- let mut state = {
|
|
|
|
- let init = ServerInitState;
|
|
|
|
- let state = init
|
|
|
|
- .handle_activate(Activate::new(rt, act_id))
|
|
|
|
- .await
|
|
|
|
- .unwrap();
|
|
|
|
- PingServerState::Listening(state)
|
|
|
|
- };
|
|
|
|
- while let Some(envelope) = mailbox.recv().await {
|
|
|
|
- let (msg, replier, _from) = envelope.split();
|
|
|
|
- match (state, msg) {
|
|
|
|
- (PingServerState::Listening(listening_state), PingProtocolMsg::Ping(msg)) => {
|
|
|
|
- let (new_state, reply) = listening_state.handle_ping(msg).await.unwrap();
|
|
|
|
- state = PingServerState::End(new_state);
|
|
|
|
- if let Err(_) = replier.unwrap().send(PingProtocolMsg::PingReply(reply)) {
|
|
|
|
- panic!("Failed to send Ping reply.");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- (_prev_state, _) => {
|
|
|
|
- panic!("Ping protocol violation.");
|
|
|
|
- // A real implementation should assign the previous state and log the error.
|
|
|
|
- // state = prev_state;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if let PingServerState::End(_) = state {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ impl ServerState {
|
|
|
|
+ fn new(counter: Arc<AtomicU8>) -> Self {
|
|
|
|
+ counter.fetch_add(1, Ordering::SeqCst);
|
|
|
|
+ Self { counter }
|
|
}
|
|
}
|
|
- counter.fetch_sub(1, Ordering::SeqCst);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- async fn ping_client(
|
|
|
|
- counter: Arc<AtomicU8>,
|
|
|
|
- server_name: ActorName,
|
|
|
|
- rt: &'static Runtime,
|
|
|
|
- _mailbox: mpsc::Receiver<Envelope<PingProtocolMsg>>,
|
|
|
|
- act_id: Uuid,
|
|
|
|
- ) {
|
|
|
|
- let init = ClientInitState;
|
|
|
|
- let (state, msg) = init
|
|
|
|
- .handle_activate(Activate::new(rt, act_id))
|
|
|
|
- .await
|
|
|
|
- .unwrap();
|
|
|
|
- let from = rt.actor_name(act_id);
|
|
|
|
- let reply = rt
|
|
|
|
- .call(server_name, from, PingProtocolMsg::Ping(msg))
|
|
|
|
- .await
|
|
|
|
- .unwrap();
|
|
|
|
- if let PingProtocolMsg::PingReply(msg) = reply {
|
|
|
|
- state.handle_reply(msg).await.unwrap();
|
|
|
|
- } else {
|
|
|
|
- panic!("Incorrect message type sent in reply to Ping.");
|
|
|
|
|
|
+ impl Server for ServerState {
|
|
|
|
+ type HandlePingFut = impl Future<Output = Result<(End, PingReply)>>;
|
|
|
|
+ fn handle_ping(self, _msg: Ping) -> Self::HandlePingFut {
|
|
|
|
+ self.counter.fetch_sub(1, Ordering::SeqCst);
|
|
|
|
+ ready(Ok((End, PingReply)))
|
|
}
|
|
}
|
|
- counter.fetch_sub(1, Ordering::SeqCst);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
fn ping_pong_test() {
|
|
fn ping_pong_test() {
|
|
ASYNC_RT.block_on(async {
|
|
ASYNC_RT.block_on(async {
|
|
- let counter = Arc::new(AtomicU8::new(2));
|
|
|
|
- let server_name = {
|
|
|
|
- let counter = counter.clone();
|
|
|
|
- RUNTIME
|
|
|
|
- .activate(move |rt, mailbox, act_id| ping_server(counter, rt, mailbox, act_id))
|
|
|
|
- .await
|
|
|
|
- };
|
|
|
|
- let client_name = {
|
|
|
|
- let server_name = server_name.clone();
|
|
|
|
- let counter = counter.clone();
|
|
|
|
- RUNTIME
|
|
|
|
- .activate(move |rt, mailbox, act_id| {
|
|
|
|
- ping_client(counter, server_name, rt, mailbox, act_id)
|
|
|
|
- })
|
|
|
|
|
|
+ const SERVICE_ID: &str = "PingPongProtocolServer";
|
|
|
|
+ let service_id = ServiceId::from(SERVICE_ID);
|
|
|
|
+ let counter = Arc::new(AtomicU8::new(0));
|
|
|
|
+ let service_name = {
|
|
|
|
+ let service_counter = counter.clone();
|
|
|
|
+ let make_init = move || {
|
|
|
|
+ let server_counter = service_counter.clone();
|
|
|
|
+ ServerState::new(server_counter)
|
|
|
|
+ };
|
|
|
|
+ register_server(make_init, &RUNTIME, service_id.clone())
|
|
.await
|
|
.await
|
|
|
|
+ .unwrap()
|
|
};
|
|
};
|
|
|
|
+ let mut client_handle = spawn_client(ClientState::new(counter.clone()), &RUNTIME).await;
|
|
|
|
+ let service_addr = ServiceAddr::new(service_name, true);
|
|
|
|
+ client_handle.send_ping(Ping, service_addr).await.unwrap();
|
|
|
|
|
|
- let deadline = Instant::now() + Duration::from_millis(500);
|
|
|
|
- while counter.load(Ordering::SeqCst) > 0 && Instant::now() < deadline {
|
|
|
|
- tokio::time::sleep(Duration::from_millis(20)).await;
|
|
|
|
- }
|
|
|
|
- // Check that both tasks finished successfully and we didn't just timeout.
|
|
|
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
|
assert_eq!(0, counter.load(Ordering::SeqCst));
|
|
|
|
|
|
- // TODO: Should actor which return be removed from the runtime automatically?
|
|
|
|
- RUNTIME.take(&server_name).await.unwrap();
|
|
|
|
- RUNTIME.take(&client_name).await.unwrap();
|
|
|
|
|
|
+ RUNTIME.take_service(&service_id).await.unwrap();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -412,10 +422,17 @@ mod travel_agency {
|
|
|
|
|
|
#[allow(dead_code)]
|
|
#[allow(dead_code)]
|
|
mod client_callback {
|
|
mod client_callback {
|
|
|
|
+
|
|
use super::*;
|
|
use super::*;
|
|
|
|
|
|
|
|
+ use std::time::Duration;
|
|
|
|
+ use tokio::{sync::oneshot, time::timeout};
|
|
|
|
+
|
|
#[derive(Serialize, Deserialize)]
|
|
#[derive(Serialize, Deserialize)]
|
|
- pub struct Register;
|
|
|
|
|
|
+ pub struct Register {
|
|
|
|
+ factor: usize,
|
|
|
|
+ }
|
|
|
|
+
|
|
#[derive(Serialize, Deserialize)]
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Completed {
|
|
pub struct Completed {
|
|
value: usize,
|
|
value: usize,
|
|
@@ -433,7 +450,7 @@ mod client_callback {
|
|
}
|
|
}
|
|
|
|
|
|
struct UnregisteredState {
|
|
struct UnregisteredState {
|
|
- factor: usize,
|
|
|
|
|
|
+ sender: oneshot::Sender<usize>,
|
|
}
|
|
}
|
|
|
|
|
|
impl Unregistered for UnregisteredState {
|
|
impl Unregistered for UnregisteredState {
|
|
@@ -441,42 +458,53 @@ mod client_callback {
|
|
type OnSendRegisterFut = Ready<Result<Self::OnSendRegisterRegistered>>;
|
|
type OnSendRegisterFut = Ready<Result<Self::OnSendRegisterRegistered>>;
|
|
fn on_send_register(self, _arg: &mut Register) -> Self::OnSendRegisterFut {
|
|
fn on_send_register(self, _arg: &mut Register) -> Self::OnSendRegisterFut {
|
|
ready(Ok(RegisteredState {
|
|
ready(Ok(RegisteredState {
|
|
- factor: self.factor,
|
|
|
|
- result: None,
|
|
|
|
|
|
+ sender: self.sender,
|
|
}))
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct RegisteredState {
|
|
struct RegisteredState {
|
|
- factor: usize,
|
|
|
|
- result: Option<usize>,
|
|
|
|
|
|
+ sender: oneshot::Sender<usize>,
|
|
}
|
|
}
|
|
|
|
|
|
impl Registered for RegisteredState {
|
|
impl Registered for RegisteredState {
|
|
type HandleCompletedFut = Ready<Result<End>>;
|
|
type HandleCompletedFut = Ready<Result<End>>;
|
|
- fn handle_completed(mut self, arg: Completed) -> Self::HandleCompletedFut {
|
|
|
|
- self.result = Some(self.factor * arg.value);
|
|
|
|
|
|
+ fn handle_completed(self, arg: Completed) -> Self::HandleCompletedFut {
|
|
|
|
+ self.sender.send(arg.value).unwrap();
|
|
ready(Ok(End))
|
|
ready(Ok(End))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- struct ListeningState;
|
|
|
|
|
|
+ struct ListeningState {
|
|
|
|
+ multiple: usize,
|
|
|
|
+ }
|
|
|
|
|
|
impl Listening for ListeningState {
|
|
impl Listening for ListeningState {
|
|
type HandleRegisterListening = ListeningState;
|
|
type HandleRegisterListening = ListeningState;
|
|
type HandleRegisterWorking = WorkingState;
|
|
type HandleRegisterWorking = WorkingState;
|
|
type HandleRegisterFut = Ready<Result<(ListeningState, WorkingState)>>;
|
|
type HandleRegisterFut = Ready<Result<(ListeningState, WorkingState)>>;
|
|
- fn handle_register(self, _arg: Register) -> Self::HandleRegisterFut {
|
|
|
|
- ready(Ok((self, WorkingState)))
|
|
|
|
|
|
+ fn handle_register(self, arg: Register) -> Self::HandleRegisterFut {
|
|
|
|
+ let multiple = self.multiple;
|
|
|
|
+ ready(Ok((
|
|
|
|
+ self,
|
|
|
|
+ WorkingState {
|
|
|
|
+ factor: arg.factor,
|
|
|
|
+ multiple,
|
|
|
|
+ },
|
|
|
|
+ )))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- struct WorkingState;
|
|
|
|
|
|
+ struct WorkingState {
|
|
|
|
+ factor: usize,
|
|
|
|
+ multiple: usize,
|
|
|
|
+ }
|
|
|
|
|
|
impl Working for WorkingState {
|
|
impl Working for WorkingState {
|
|
type OnSendCompletedFut = Ready<Result<(End, Completed)>>;
|
|
type OnSendCompletedFut = Ready<Result<(End, Completed)>>;
|
|
fn on_send_completed(self) -> Self::OnSendCompletedFut {
|
|
fn on_send_completed(self) -> Self::OnSendCompletedFut {
|
|
- ready(Ok((End, Completed { value: 42 })))
|
|
|
|
|
|
+ let value = self.multiple * self.factor;
|
|
|
|
+ ready(Ok((End, Completed { value })))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -505,7 +533,7 @@ mod client_callback {
|
|
}
|
|
}
|
|
|
|
|
|
impl<Init: Unregistered> ClientHandle<Init> {
|
|
impl<Init: Unregistered> ClientHandle<Init> {
|
|
- async fn send_register(&self, to: ServiceName, mut msg: Register) -> Result<()> {
|
|
|
|
|
|
+ async fn send_register(&self, to: ServiceAddr, mut msg: Register) -> Result<()> {
|
|
let mut guard = self.state.lock().await;
|
|
let mut guard = self.state.lock().await;
|
|
let state = guard
|
|
let state = guard
|
|
.take()
|
|
.take()
|
|
@@ -528,19 +556,19 @@ mod client_callback {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- async fn start_client<Init>(init: Init, runtime: &'static Runtime) -> ClientHandle<Init>
|
|
|
|
|
|
+ async fn spawn_client<Init>(init: Init, runtime: &'static Runtime) -> ClientHandle<Init>
|
|
where
|
|
where
|
|
Init: 'static + Unregistered,
|
|
Init: 'static + Unregistered,
|
|
{
|
|
{
|
|
let state = Arc::new(Mutex::new(Some(ClientState::Unregistered(init))));
|
|
let state = Arc::new(Mutex::new(Some(ClientState::Unregistered(init))));
|
|
let name = {
|
|
let name = {
|
|
let state = state.clone();
|
|
let state = state.clone();
|
|
- runtime.activate(move |_, mut mailbox, _act_id| async move {
|
|
|
|
|
|
+ runtime.spawn(move |_, mut mailbox, _act_id| async move {
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
let mut guard = state.lock().await;
|
|
let mut guard = state.lock().await;
|
|
let state = guard.take()
|
|
let state = guard.take()
|
|
.unwrap_or_else(|| panic!("Logic error. The state was not returned."));
|
|
.unwrap_or_else(|| panic!("Logic error. The state was not returned."));
|
|
- let (msg, _replier, _from) = envelope.split();
|
|
|
|
|
|
+ let (msg, _kind) = envelope.split();
|
|
let new_state = match (state, msg) {
|
|
let new_state = match (state, msg) {
|
|
(ClientState::Registered(curr_state), ClientCallbackMsgs::Completed(msg)) => {
|
|
(ClientState::Registered(curr_state), ClientCallbackMsgs::Completed(msg)) => {
|
|
match curr_state.handle_completed(msg).await {
|
|
match curr_state.handle_completed(msg).await {
|
|
@@ -567,9 +595,14 @@ mod client_callback {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- async fn start_server<Init>(init: Init, runtime: &'static Runtime) -> ActorName
|
|
|
|
|
|
+ async fn register_server<Init, F>(
|
|
|
|
+ make_init: F,
|
|
|
|
+ runtime: &'static Runtime,
|
|
|
|
+ service_id: ServiceId,
|
|
|
|
+ ) -> Result<ServiceName>
|
|
where
|
|
where
|
|
Init: 'static + Listening<HandleRegisterListening = Init>,
|
|
Init: 'static + Listening<HandleRegisterListening = Init>,
|
|
|
|
+ F: 'static + Send + Sync + Clone + Fn() -> Init,
|
|
{
|
|
{
|
|
enum ServerState<S: Listening> {
|
|
enum ServerState<S: Listening> {
|
|
Listening(S),
|
|
Listening(S),
|
|
@@ -583,35 +616,61 @@ mod client_callback {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- runtime
|
|
|
|
- .activate(move |_, mut mailbox, _act_id| async move {
|
|
|
|
- let mut state = ServerState::Listening(init);
|
|
|
|
- while let Some(envelope) = mailbox.recv().await {
|
|
|
|
- let (msg, _replier, from) = envelope.split();
|
|
|
|
- let new_state = match (state, msg) {
|
|
|
|
- (ServerState::Listening(curr_state), ClientCallbackMsgs::Register(msg)) => {
|
|
|
|
- match curr_state.handle_register(msg).await {
|
|
|
|
- Ok((new_state, working_state)) => {
|
|
|
|
|
|
+ async fn server_loop<Init, F>(
|
|
|
|
+ runtime: &'static Runtime,
|
|
|
|
+ make_init: F,
|
|
|
|
+ mut mailbox: Mailbox<ClientCallbackMsgs>,
|
|
|
|
+ _act_id: Uuid,
|
|
|
|
+ ) where
|
|
|
|
+ Init: 'static + Listening<HandleRegisterListening = Init>,
|
|
|
|
+ F: 'static + Send + Sync + Fn() -> Init,
|
|
|
|
+ {
|
|
|
|
+ let mut state = ServerState::Listening(make_init());
|
|
|
|
+ while let Some(envelope) = mailbox.recv().await {
|
|
|
|
+ let (msg, msg_kind) = envelope.split();
|
|
|
|
+ let new_state = match (state, msg) {
|
|
|
|
+ (ServerState::Listening(curr_state), ClientCallbackMsgs::Register(msg)) => {
|
|
|
|
+ match curr_state.handle_register(msg).await {
|
|
|
|
+ Ok((new_state, working_state)) => {
|
|
|
|
+ if let EnvelopeKind::Send { from, .. } = msg_kind {
|
|
start_worker(working_state, from, runtime).await;
|
|
start_worker(working_state, from, runtime).await;
|
|
- ServerState::Listening(new_state)
|
|
|
|
- }
|
|
|
|
- Err(err) => {
|
|
|
|
- log::error!("Failed to handle the Register message: {err}");
|
|
|
|
- todo!("Need to recover the previous state from err.")
|
|
|
|
|
|
+ } else {
|
|
|
|
+ log::error!("Expected Register to be a Send message.");
|
|
}
|
|
}
|
|
|
|
+ ServerState::Listening(new_state)
|
|
|
|
+ }
|
|
|
|
+ Err(err) => {
|
|
|
|
+ log::error!("Failed to handle the Register message: {err}");
|
|
|
|
+ todo!("Need to recover the previous state from err.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- (state, msg) => {
|
|
|
|
- log::error!(
|
|
|
|
- "Unexpected message '{}' in state '{}'.",
|
|
|
|
- msg.name(),
|
|
|
|
- state.name()
|
|
|
|
- );
|
|
|
|
- state
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
- state = new_state;
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+ (state, msg) => {
|
|
|
|
+ log::error!(
|
|
|
|
+ "Unexpected message '{}' in state '{}'.",
|
|
|
|
+ msg.name(),
|
|
|
|
+ state.name()
|
|
|
|
+ );
|
|
|
|
+ state
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ state = new_state;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ runtime
|
|
|
|
+ .register::<ClientCallbackMsgs, _>(service_id, move |runtime: &'static Runtime| {
|
|
|
|
+ let make_init = make_init.clone();
|
|
|
|
+ let fut = async move {
|
|
|
|
+ let make_init = make_init.clone();
|
|
|
|
+ let actor_name = runtime
|
|
|
|
+ .spawn(move |_, mailbox, act_id| {
|
|
|
|
+ server_loop(runtime, make_init, mailbox, act_id)
|
|
|
|
+ })
|
|
|
|
+ .await;
|
|
|
|
+ Ok(actor_name)
|
|
|
|
+ };
|
|
|
|
+ Box::pin(fut)
|
|
})
|
|
})
|
|
.await
|
|
.await
|
|
}
|
|
}
|
|
@@ -629,7 +688,7 @@ mod client_callback {
|
|
}
|
|
}
|
|
|
|
|
|
runtime
|
|
runtime
|
|
- .activate::<ClientCallbackMsgs, _, _>(move |_, _, act_id| async move {
|
|
|
|
|
|
+ .spawn::<ClientCallbackMsgs, _, _>(move |_, _, act_id| async move {
|
|
let msg = match init.on_send_completed().await {
|
|
let msg = match init.on_send_completed().await {
|
|
Ok((End, msg)) => msg,
|
|
Ok((End, msg)) => msg,
|
|
Err(err) => {
|
|
Err(err) => {
|
|
@@ -645,4 +704,31 @@ mod client_callback {
|
|
})
|
|
})
|
|
.await
|
|
.await
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ #[test]
|
|
|
|
+ fn client_callback_protocol() {
|
|
|
|
+ ASYNC_RT.block_on(async {
|
|
|
|
+ const SERVICE_ID: &str = "ClientCallbackProtocolListening";
|
|
|
|
+ let service_id = ServiceId::from(SERVICE_ID);
|
|
|
|
+ let service_name = {
|
|
|
|
+ let make_init = move || ListeningState { multiple: 2 };
|
|
|
|
+ register_server(make_init, &RUNTIME, service_id.clone())
|
|
|
|
+ .await
|
|
|
|
+ .unwrap()
|
|
|
|
+ };
|
|
|
|
+ let (sender, receiver) = oneshot::channel();
|
|
|
|
+ let client_handle = spawn_client(UnregisteredState { sender }, &RUNTIME).await;
|
|
|
|
+ let service_addr = ServiceAddr::new(service_name, false);
|
|
|
|
+ client_handle
|
|
|
|
+ .send_register(service_addr, Register { factor: 21 })
|
|
|
|
+ .await
|
|
|
|
+ .unwrap();
|
|
|
|
+ let value = timeout(Duration::from_millis(500), receiver)
|
|
|
|
+ .await
|
|
|
|
+ .unwrap()
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ assert_eq!(42, value);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
}
|
|
}
|