forked from ROMEO/nexosim
Initial (g)RPC implementation
This commit is contained in:
50
asynchronix/src/rpc/api/custom_transport.proto
Normal file
50
asynchronix/src/rpc/api/custom_transport.proto
Normal file
@ -0,0 +1,50 @@
|
||||
// Additional types for transport implementations which, unlike gRPC, do not
|
||||
// support auto-generation from the `Simulation` service description.
|
||||
|
||||
syntax = "proto3";
|
||||
package custom_transport;
|
||||
|
||||
import "simulation.proto";
|
||||
|
||||
enum ServerErrorCode {
|
||||
UNKNOWN_REQUEST = 0;
|
||||
EMPTY_REQUEST = 1;
|
||||
}
|
||||
|
||||
message ServerError {
|
||||
ServerErrorCode code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message AnyRequest {
|
||||
oneof request { // Expects exactly 1 variant.
|
||||
simulation.InitRequest init_request = 1;
|
||||
simulation.TimeRequest time_request = 2;
|
||||
simulation.StepRequest step_request = 3;
|
||||
simulation.StepUntilRequest step_until_request = 4;
|
||||
simulation.ScheduleEventRequest schedule_event_request = 5;
|
||||
simulation.CancelEventRequest cancel_event_request = 6;
|
||||
simulation.ProcessEventRequest process_event_request = 7;
|
||||
simulation.ProcessQueryRequest process_query_request = 8;
|
||||
simulation.ReadEventsRequest read_events_request = 9;
|
||||
simulation.OpenSinkRequest open_sink_request = 10;
|
||||
simulation.CloseSinkRequest close_sink_request = 11;
|
||||
}
|
||||
}
|
||||
|
||||
message AnyReply {
|
||||
oneof reply { // Contains exactly 1 variant.
|
||||
simulation.InitReply init_reply = 1;
|
||||
simulation.TimeReply time_reply = 2;
|
||||
simulation.StepReply step_reply = 3;
|
||||
simulation.StepUntilReply step_until_reply = 4;
|
||||
simulation.ScheduleEventReply schedule_event_reply = 5;
|
||||
simulation.CancelEventReply cancel_event_reply = 6;
|
||||
simulation.ProcessEventReply process_event_reply = 7;
|
||||
simulation.ProcessQueryReply process_query_reply = 8;
|
||||
simulation.ReadEventsReply read_events_reply = 9;
|
||||
simulation.OpenSinkReply open_sink_reply = 10;
|
||||
simulation.CloseSinkReply close_sink_reply = 11;
|
||||
ServerError error = 100;
|
||||
}
|
||||
}
|
161
asynchronix/src/rpc/api/simulation.proto
Normal file
161
asynchronix/src/rpc/api/simulation.proto
Normal file
@ -0,0 +1,161 @@
|
||||
// The main simulation protocol.
|
||||
|
||||
syntax = "proto3";
|
||||
package simulation;
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
enum ErrorCode {
|
||||
INTERNAL_ERROR = 0;
|
||||
SIMULATION_NOT_STARTED = 1;
|
||||
MISSING_ARGUMENT = 2;
|
||||
INVALID_TIME = 3;
|
||||
INVALID_DURATION = 4;
|
||||
INVALID_MESSAGE = 5;
|
||||
INVALID_KEY = 6;
|
||||
SOURCE_NOT_FOUND = 10;
|
||||
SINK_NOT_FOUND = 11;
|
||||
KEY_NOT_FOUND = 12;
|
||||
SIMULATION_TIME_OUT_OF_RANGE = 13;
|
||||
}
|
||||
|
||||
message Error {
|
||||
ErrorCode code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message EventKey {
|
||||
uint64 subkey1 = 1;
|
||||
uint64 subkey2 = 2;
|
||||
}
|
||||
|
||||
message InitRequest { optional google.protobuf.Timestamp time = 1; }
|
||||
message InitReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message TimeRequest {}
|
||||
message TimeReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Timestamp time = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message StepRequest {}
|
||||
message StepReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Timestamp time = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message StepUntilRequest {
|
||||
oneof deadline { // Always returns exactly 1 variant.
|
||||
google.protobuf.Timestamp time = 1;
|
||||
google.protobuf.Duration duration = 2;
|
||||
}
|
||||
}
|
||||
message StepUntilReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Timestamp time = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message ScheduleEventRequest {
|
||||
oneof deadline { // Expects exactly 1 variant.
|
||||
google.protobuf.Timestamp time = 1;
|
||||
google.protobuf.Duration duration = 2;
|
||||
}
|
||||
string source_name = 3;
|
||||
bytes event = 4;
|
||||
optional google.protobuf.Duration period = 5;
|
||||
optional bool with_key = 6;
|
||||
}
|
||||
message ScheduleEventReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 1;
|
||||
EventKey key = 2;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message CancelEventRequest { EventKey key = 1; }
|
||||
message CancelEventReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message ProcessEventRequest {
|
||||
string source_name = 1;
|
||||
bytes event = 2;
|
||||
}
|
||||
message ProcessEventReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 1;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message ProcessQueryRequest {
|
||||
string source_name = 1;
|
||||
bytes request = 2;
|
||||
}
|
||||
message ProcessQueryReply {
|
||||
// This field is hoisted because protobuf3 does not support `repeated` within
|
||||
// a `oneof`. It is Always empty if an error is returned
|
||||
repeated bytes replies = 1;
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 10;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message ReadEventsRequest { string sink_name = 1; }
|
||||
message ReadEventsReply {
|
||||
// This field is hoisted because protobuf3 does not support `repeated` within
|
||||
// a `oneof`. It is Always empty if an error is returned
|
||||
repeated bytes events = 1;
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 10;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenSinkRequest { string sink_name = 1; }
|
||||
message OpenSinkReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 10;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message CloseSinkRequest { string sink_name = 1; }
|
||||
message CloseSinkReply {
|
||||
oneof result { // Always returns exactly 1 variant.
|
||||
google.protobuf.Empty empty = 10;
|
||||
Error error = 100;
|
||||
}
|
||||
}
|
||||
|
||||
service Simulation {
|
||||
rpc Init(InitRequest) returns (InitReply);
|
||||
rpc Time(TimeRequest) returns (TimeReply);
|
||||
rpc Step(StepRequest) returns (StepReply);
|
||||
rpc StepUntil(StepUntilRequest) returns (StepUntilReply);
|
||||
rpc ScheduleEvent(ScheduleEventRequest) returns (ScheduleEventReply);
|
||||
rpc CancelEvent(CancelEventRequest) returns (CancelEventReply);
|
||||
rpc ProcessEvent(ProcessEventRequest) returns (ProcessEventReply);
|
||||
rpc ProcessQuery(ProcessQueryRequest) returns (ProcessQueryReply);
|
||||
rpc ReadEvents(ReadEventsRequest) returns (ReadEventsReply);
|
||||
rpc OpenSink(OpenSinkRequest) returns (OpenSinkReply);
|
||||
rpc CloseSink(CloseSinkRequest) returns (CloseSinkReply);
|
||||
}
|
5
asynchronix/src/rpc/codegen.rs
Normal file
5
asynchronix/src/rpc/codegen.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#![allow(unreachable_pub)]
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
|
||||
pub(crate) mod custom_transport;
|
||||
pub(crate) mod simulation;
|
0
asynchronix/src/rpc/codegen/.gitkeep
Normal file
0
asynchronix/src/rpc/codegen/.gitkeep
Normal file
111
asynchronix/src/rpc/codegen/custom_transport.rs
Normal file
111
asynchronix/src/rpc/codegen/custom_transport.rs
Normal file
@ -0,0 +1,111 @@
|
||||
// This file is @generated by prost-build.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ServerError {
|
||||
#[prost(enumeration = "ServerErrorCode", tag = "1")]
|
||||
pub code: i32,
|
||||
#[prost(string, tag = "2")]
|
||||
pub message: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AnyRequest {
|
||||
/// Expects exactly 1 variant.
|
||||
#[prost(oneof = "any_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")]
|
||||
pub request: ::core::option::Option<any_request::Request>,
|
||||
}
|
||||
/// Nested message and enum types in `AnyRequest`.
|
||||
pub mod any_request {
|
||||
/// Expects exactly 1 variant.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Request {
|
||||
#[prost(message, tag = "1")]
|
||||
InitRequest(super::super::simulation::InitRequest),
|
||||
#[prost(message, tag = "2")]
|
||||
TimeRequest(super::super::simulation::TimeRequest),
|
||||
#[prost(message, tag = "3")]
|
||||
StepRequest(super::super::simulation::StepRequest),
|
||||
#[prost(message, tag = "4")]
|
||||
StepUntilRequest(super::super::simulation::StepUntilRequest),
|
||||
#[prost(message, tag = "5")]
|
||||
ScheduleEventRequest(super::super::simulation::ScheduleEventRequest),
|
||||
#[prost(message, tag = "6")]
|
||||
CancelEventRequest(super::super::simulation::CancelEventRequest),
|
||||
#[prost(message, tag = "7")]
|
||||
ProcessEventRequest(super::super::simulation::ProcessEventRequest),
|
||||
#[prost(message, tag = "8")]
|
||||
ProcessQueryRequest(super::super::simulation::ProcessQueryRequest),
|
||||
#[prost(message, tag = "9")]
|
||||
ReadEventsRequest(super::super::simulation::ReadEventsRequest),
|
||||
#[prost(message, tag = "10")]
|
||||
OpenSinkRequest(super::super::simulation::OpenSinkRequest),
|
||||
#[prost(message, tag = "11")]
|
||||
CloseSinkRequest(super::super::simulation::CloseSinkRequest),
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AnyReply {
|
||||
/// Contains exactly 1 variant.
|
||||
#[prost(oneof = "any_reply::Reply", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 100")]
|
||||
pub reply: ::core::option::Option<any_reply::Reply>,
|
||||
}
|
||||
/// Nested message and enum types in `AnyReply`.
|
||||
pub mod any_reply {
|
||||
/// Contains exactly 1 variant.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Reply {
|
||||
#[prost(message, tag = "1")]
|
||||
InitReply(super::super::simulation::InitReply),
|
||||
#[prost(message, tag = "2")]
|
||||
TimeReply(super::super::simulation::TimeReply),
|
||||
#[prost(message, tag = "3")]
|
||||
StepReply(super::super::simulation::StepReply),
|
||||
#[prost(message, tag = "4")]
|
||||
StepUntilReply(super::super::simulation::StepUntilReply),
|
||||
#[prost(message, tag = "5")]
|
||||
ScheduleEventReply(super::super::simulation::ScheduleEventReply),
|
||||
#[prost(message, tag = "6")]
|
||||
CancelEventReply(super::super::simulation::CancelEventReply),
|
||||
#[prost(message, tag = "7")]
|
||||
ProcessEventReply(super::super::simulation::ProcessEventReply),
|
||||
#[prost(message, tag = "8")]
|
||||
ProcessQueryReply(super::super::simulation::ProcessQueryReply),
|
||||
#[prost(message, tag = "9")]
|
||||
ReadEventsReply(super::super::simulation::ReadEventsReply),
|
||||
#[prost(message, tag = "10")]
|
||||
OpenSinkReply(super::super::simulation::OpenSinkReply),
|
||||
#[prost(message, tag = "11")]
|
||||
CloseSinkReply(super::super::simulation::CloseSinkReply),
|
||||
#[prost(message, tag = "100")]
|
||||
Error(super::ServerError),
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum ServerErrorCode {
|
||||
UnknownRequest = 0,
|
||||
EmptyRequest = 1,
|
||||
}
|
||||
impl ServerErrorCode {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
///
|
||||
/// The values are not transformed in any way and thus are considered stable
|
||||
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
ServerErrorCode::UnknownRequest => "UNKNOWN_REQUEST",
|
||||
ServerErrorCode::EmptyRequest => "EMPTY_REQUEST",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"UNKNOWN_REQUEST" => Some(Self::UnknownRequest),
|
||||
"EMPTY_REQUEST" => Some(Self::EmptyRequest),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
1071
asynchronix/src/rpc/codegen/simulation.rs
Normal file
1071
asynchronix/src/rpc/codegen/simulation.rs
Normal file
File diff suppressed because it is too large
Load Diff
307
asynchronix/src/rpc/endpoint_registry.rs
Normal file
307
asynchronix/src/rpc/endpoint_registry.rs
Normal file
@ -0,0 +1,307 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
use rmp_serde::decode::Error as RmpDecodeError;
|
||||
use rmp_serde::encode::Error as RmpEncodeError;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ports::{EventSinkStream, EventSource, QuerySource, ReplyReceiver};
|
||||
use crate::time::{Action, ActionKey};
|
||||
|
||||
/// A registry that holds all sources and sinks meant to be accessed through
|
||||
/// remote procedure calls.
|
||||
#[derive(Default)]
|
||||
pub struct EndpointRegistry {
|
||||
event_sources: HashMap<String, Box<dyn EventSourceAny>>,
|
||||
query_sources: HashMap<String, Box<dyn QuerySourceAny>>,
|
||||
sinks: HashMap<String, Box<dyn EventSinkStreamAny>>,
|
||||
}
|
||||
|
||||
impl EndpointRegistry {
|
||||
/// Creates an empty `EndpointRegistry`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Adds an event source to the registry.
|
||||
///
|
||||
/// If the specified name is already in use for another event source, the source
|
||||
/// provided as argument is returned in the error.
|
||||
pub fn add_event_source<T>(
|
||||
&mut self,
|
||||
source: EventSource<T>,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), EventSource<T>>
|
||||
where
|
||||
T: DeserializeOwned + Clone + Send + 'static,
|
||||
{
|
||||
match self.event_sources.entry(name.into()) {
|
||||
Entry::Vacant(s) => {
|
||||
s.insert(Box::new(source));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Entry::Occupied(_) => Err(source),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the specified event source if it is in
|
||||
/// the registry.
|
||||
pub(crate) fn get_event_source_mut(&mut self, name: &str) -> Option<&mut dyn EventSourceAny> {
|
||||
self.event_sources.get_mut(name).map(|s| s.as_mut())
|
||||
}
|
||||
|
||||
/// Adds an query source to the registry.
|
||||
///
|
||||
/// If the specified name is already in use for another query source, the source
|
||||
/// provided as argument is returned in the error.
|
||||
pub fn add_query_source<T, R>(
|
||||
&mut self,
|
||||
source: QuerySource<T, R>,
|
||||
name: impl Into<String>,
|
||||
) -> Result<(), QuerySource<T, R>>
|
||||
where
|
||||
T: DeserializeOwned + Clone + Send + 'static,
|
||||
R: Serialize + Send + 'static,
|
||||
{
|
||||
match self.query_sources.entry(name.into()) {
|
||||
Entry::Vacant(s) => {
|
||||
s.insert(Box::new(source));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Entry::Occupied(_) => Err(source),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the specified query source if it is in
|
||||
/// the registry.
|
||||
pub(crate) fn get_query_source_mut(&mut self, name: &str) -> Option<&mut dyn QuerySourceAny> {
|
||||
self.query_sources.get_mut(name).map(|s| s.as_mut())
|
||||
}
|
||||
|
||||
/// Adds a sink to the registry.
|
||||
///
|
||||
/// If the specified name is already in use for another sink, the sink
|
||||
/// provided as argument is returned in the error.
|
||||
pub fn add_sink<S>(&mut self, sink: S, name: impl Into<String>) -> Result<(), S>
|
||||
where
|
||||
S: EventSinkStream + Send + 'static,
|
||||
S::Item: Serialize,
|
||||
{
|
||||
match self.sinks.entry(name.into()) {
|
||||
Entry::Vacant(s) => {
|
||||
s.insert(Box::new(sink));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Entry::Occupied(_) => Err(sink),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the specified sink if it is in the
|
||||
/// registry.
|
||||
pub(crate) fn get_sink_mut(&mut self, name: &str) -> Option<&mut dyn EventSinkStreamAny> {
|
||||
self.sinks.get_mut(name).map(|s| s.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EndpointRegistry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"EndpointRegistry ({} sources, {} sinks)",
|
||||
self.event_sources.len(),
|
||||
self.sinks.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased `EventSource` that operates on MessagePack-encoded serialized
|
||||
/// events.
|
||||
pub(crate) trait EventSourceAny: Send + 'static {
|
||||
/// Returns an action which, when processed, broadcasts an event to all
|
||||
/// connected input ports.
|
||||
///
|
||||
/// The argument is expected to conform to the serde MessagePack encoding.
|
||||
fn event(&mut self, msgpack_arg: &[u8]) -> Result<Action, RmpDecodeError>;
|
||||
|
||||
/// Returns a cancellable action and a cancellation key; when processed, the
|
||||
/// action broadcasts an event to all connected input ports.
|
||||
///
|
||||
/// The argument is expected to conform to the serde MessagePack encoding.
|
||||
fn keyed_event(&mut self, msgpack_arg: &[u8]) -> Result<(Action, ActionKey), RmpDecodeError>;
|
||||
|
||||
/// Returns a periodically recurring action which, when processed,
|
||||
/// broadcasts an event to all connected input ports.
|
||||
///
|
||||
/// The argument is expected to conform to the serde MessagePack encoding.
|
||||
fn periodic_event(
|
||||
&mut self,
|
||||
period: Duration,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<Action, RmpDecodeError>;
|
||||
|
||||
/// Returns a cancellable, periodically recurring action and a cancellation
|
||||
/// key; when processed, the action broadcasts an event to all connected
|
||||
/// input ports.
|
||||
///
|
||||
/// The argument is expected to conform to the serde MessagePack encoding.
|
||||
fn keyed_periodic_event(
|
||||
&mut self,
|
||||
period: Duration,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<(Action, ActionKey), RmpDecodeError>;
|
||||
|
||||
/// Human-readable name of the event type, as returned by
|
||||
/// `any::type_name()`.
|
||||
fn event_type_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl<T> EventSourceAny for EventSource<T>
|
||||
where
|
||||
T: DeserializeOwned + Clone + Send + 'static,
|
||||
{
|
||||
fn event(&mut self, msgpack_arg: &[u8]) -> Result<Action, RmpDecodeError> {
|
||||
rmp_serde::from_read(msgpack_arg).map(|arg| self.event(arg))
|
||||
}
|
||||
fn keyed_event(&mut self, msgpack_arg: &[u8]) -> Result<(Action, ActionKey), RmpDecodeError> {
|
||||
rmp_serde::from_read(msgpack_arg).map(|arg| self.keyed_event(arg))
|
||||
}
|
||||
fn periodic_event(
|
||||
&mut self,
|
||||
period: Duration,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<Action, RmpDecodeError> {
|
||||
rmp_serde::from_read(msgpack_arg).map(|arg| self.periodic_event(period, arg))
|
||||
}
|
||||
fn keyed_periodic_event(
|
||||
&mut self,
|
||||
period: Duration,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<(Action, ActionKey), RmpDecodeError> {
|
||||
rmp_serde::from_read(msgpack_arg).map(|arg| self.keyed_periodic_event(period, arg))
|
||||
}
|
||||
fn event_type_name(&self) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased `QuerySource` that operates on MessagePack-encoded serialized
|
||||
/// queries and returns MessagePack-encoded replies.
|
||||
pub(crate) trait QuerySourceAny: Send + 'static {
|
||||
/// Returns an action which, when processed, broadcasts a query to all
|
||||
/// connected replier ports.
|
||||
///
|
||||
///
|
||||
/// The argument is expected to conform to the serde MessagePack encoding.
|
||||
fn query(
|
||||
&mut self,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<(Action, Box<dyn ReplyReceiverAny>), RmpDecodeError>;
|
||||
|
||||
/// Human-readable name of the request type, as returned by
|
||||
/// `any::type_name()`.
|
||||
fn request_type_name(&self) -> &'static str;
|
||||
|
||||
/// Human-readable name of the reply type, as returned by
|
||||
/// `any::type_name()`.
|
||||
fn reply_type_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl<T, R> QuerySourceAny for QuerySource<T, R>
|
||||
where
|
||||
T: DeserializeOwned + Clone + Send + 'static,
|
||||
R: Serialize + Send + 'static,
|
||||
{
|
||||
fn query(
|
||||
&mut self,
|
||||
msgpack_arg: &[u8],
|
||||
) -> Result<(Action, Box<dyn ReplyReceiverAny>), RmpDecodeError> {
|
||||
rmp_serde::from_read(msgpack_arg).map(|arg| {
|
||||
let (action, reply_recv) = self.query(arg);
|
||||
let reply_recv: Box<dyn ReplyReceiverAny> = Box::new(reply_recv);
|
||||
|
||||
(action, reply_recv)
|
||||
})
|
||||
}
|
||||
|
||||
fn request_type_name(&self) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
|
||||
fn reply_type_name(&self) -> &'static str {
|
||||
std::any::type_name::<R>()
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased `EventSinkStream`.
|
||||
pub(crate) trait EventSinkStreamAny: Send + 'static {
|
||||
/// Human-readable name of the event type, as returned by
|
||||
/// `any::type_name()`.
|
||||
fn event_type_name(&self) -> &'static str;
|
||||
|
||||
/// Starts or resumes the collection of new events.
|
||||
fn open(&mut self);
|
||||
|
||||
/// Pauses the collection of new events.
|
||||
fn close(&mut self);
|
||||
|
||||
/// Encode and collect all events in a vector.
|
||||
fn collect(&mut self) -> Result<Vec<Vec<u8>>, RmpEncodeError>;
|
||||
}
|
||||
|
||||
impl<E> EventSinkStreamAny for E
|
||||
where
|
||||
E: EventSinkStream + Send + 'static,
|
||||
E::Item: Serialize,
|
||||
{
|
||||
fn event_type_name(&self) -> &'static str {
|
||||
std::any::type_name::<E::Item>()
|
||||
}
|
||||
|
||||
fn open(&mut self) {
|
||||
self.open();
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
|
||||
fn collect(&mut self) -> Result<Vec<Vec<u8>>, RmpEncodeError> {
|
||||
EventSinkStream::try_fold(self, Vec::new(), |mut encoded_events, event| {
|
||||
rmp_serde::to_vec_named(&event).map(|encoded_event| {
|
||||
encoded_events.push(encoded_event);
|
||||
|
||||
encoded_events
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased `ReplyReceiver` that returns MessagePack-encoded replies..
|
||||
pub(crate) trait ReplyReceiverAny {
|
||||
/// Take the replies, if any, encode them and collect them in a vector.
|
||||
fn take_collect(&mut self) -> Option<Result<Vec<Vec<u8>>, RmpEncodeError>>;
|
||||
}
|
||||
|
||||
impl<R: Serialize + 'static> ReplyReceiverAny for ReplyReceiver<R> {
|
||||
fn take_collect(&mut self) -> Option<Result<Vec<Vec<u8>>, RmpEncodeError>> {
|
||||
let replies = self.take()?;
|
||||
|
||||
let encoded_replies = (move || {
|
||||
let mut encoded_replies = Vec::new();
|
||||
for reply in replies {
|
||||
let encoded_reply = rmp_serde::to_vec_named(&reply)?;
|
||||
encoded_replies.push(encoded_reply);
|
||||
}
|
||||
|
||||
Ok(encoded_replies)
|
||||
})();
|
||||
|
||||
Some(encoded_replies)
|
||||
}
|
||||
}
|
673
asynchronix/src/rpc/generic_server.rs
Normal file
673
asynchronix/src/rpc/generic_server.rs
Normal file
@ -0,0 +1,673 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Buf;
|
||||
use prost::Message;
|
||||
use prost_types::Timestamp;
|
||||
use tai_time::MonotonicTime;
|
||||
|
||||
use crate::rpc::key_registry::{KeyRegistry, KeyRegistryId};
|
||||
use crate::rpc::EndpointRegistry;
|
||||
use crate::simulation::{SimInit, Simulation};
|
||||
|
||||
use super::codegen::custom_transport::*;
|
||||
use super::codegen::simulation::*;
|
||||
|
||||
/// Transport-independent server implementation.
|
||||
///
|
||||
/// This implementation implements the protobuf services without any
|
||||
/// transport-specific management.
|
||||
pub(crate) struct GenericServer<F> {
|
||||
sim_gen: F,
|
||||
sim_context: Option<(Simulation, EndpointRegistry, KeyRegistry)>,
|
||||
}
|
||||
|
||||
impl<F> GenericServer<F>
|
||||
where
|
||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||
{
|
||||
/// Creates a new `GenericServer` without any active simulation.
|
||||
pub(crate) fn new(sim_gen: F) -> Self {
|
||||
Self {
|
||||
sim_gen,
|
||||
sim_context: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an encoded `AnyRequest` message and returns an encoded
|
||||
/// `AnyReply`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn service_request<B>(&mut self, request_buf: B) -> Vec<u8>
|
||||
where
|
||||
B: Buf,
|
||||
{
|
||||
let reply = match AnyRequest::decode(request_buf) {
|
||||
Ok(AnyRequest { request: Some(req) }) => match req {
|
||||
any_request::Request::InitRequest(request) => {
|
||||
any_reply::Reply::InitReply(self.init(request))
|
||||
}
|
||||
any_request::Request::TimeRequest(request) => {
|
||||
any_reply::Reply::TimeReply(self.time(request))
|
||||
}
|
||||
any_request::Request::StepRequest(request) => {
|
||||
any_reply::Reply::StepReply(self.step(request))
|
||||
}
|
||||
any_request::Request::StepUntilRequest(request) => {
|
||||
any_reply::Reply::StepUntilReply(self.step_until(request))
|
||||
}
|
||||
any_request::Request::ScheduleEventRequest(request) => {
|
||||
any_reply::Reply::ScheduleEventReply(self.schedule_event(request))
|
||||
}
|
||||
any_request::Request::CancelEventRequest(request) => {
|
||||
any_reply::Reply::CancelEventReply(self.cancel_event(request))
|
||||
}
|
||||
any_request::Request::ProcessEventRequest(request) => {
|
||||
any_reply::Reply::ProcessEventReply(self.process_event(request))
|
||||
}
|
||||
any_request::Request::ProcessQueryRequest(request) => {
|
||||
any_reply::Reply::ProcessQueryReply(self.process_query(request))
|
||||
}
|
||||
any_request::Request::ReadEventsRequest(request) => {
|
||||
any_reply::Reply::ReadEventsReply(self.read_events(request))
|
||||
}
|
||||
any_request::Request::OpenSinkRequest(request) => {
|
||||
any_reply::Reply::OpenSinkReply(self.open_sink(request))
|
||||
}
|
||||
any_request::Request::CloseSinkRequest(request) => {
|
||||
any_reply::Reply::CloseSinkReply(self.close_sink(request))
|
||||
}
|
||||
},
|
||||
Ok(AnyRequest { request: None }) => any_reply::Reply::Error(ServerError {
|
||||
code: ServerErrorCode::EmptyRequest as i32,
|
||||
message: "the message did not contain any request".to_string(),
|
||||
}),
|
||||
Err(err) => any_reply::Reply::Error(ServerError {
|
||||
code: ServerErrorCode::UnknownRequest as i32,
|
||||
message: format!("bad request: {}", err),
|
||||
}),
|
||||
};
|
||||
|
||||
let reply = AnyReply { reply: Some(reply) };
|
||||
|
||||
reply.encode_to_vec()
|
||||
}
|
||||
|
||||
/// Initialize a simulation with the provided time.
|
||||
///
|
||||
/// If a simulation is already active, it is destructed and replaced with a
|
||||
/// new simulation.
|
||||
///
|
||||
/// If the initialization time is not provided, it is initialized with the
|
||||
/// epoch of `MonotonicTime` (1970-01-01 00:00:00 TAI).
|
||||
pub(crate) fn init(&mut self, request: InitRequest) -> InitReply {
|
||||
let start_time = request.time.unwrap_or_default();
|
||||
let reply = if let Some(start_time) = timestamp_to_monotonic(start_time) {
|
||||
let (sim_init, endpoint_registry) = (self.sim_gen)();
|
||||
let simulation = sim_init.init(start_time);
|
||||
self.sim_context = Some((simulation, endpoint_registry, KeyRegistry::default()));
|
||||
|
||||
init_reply::Result::Empty(())
|
||||
} else {
|
||||
init_reply::Result::Error(Error {
|
||||
code: ErrorCode::InvalidTime as i32,
|
||||
message: "out-of-range nanosecond field".to_string(),
|
||||
})
|
||||
};
|
||||
|
||||
InitReply {
|
||||
result: Some(reply),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current simulation time.
|
||||
pub(crate) fn time(&mut self, _request: TimeRequest) -> TimeReply {
|
||||
let reply = match &self.sim_context {
|
||||
Some((simulation, ..)) => {
|
||||
if let Some(timestamp) = monotonic_to_timestamp(simulation.time()) {
|
||||
time_reply::Result::Time(timestamp)
|
||||
} else {
|
||||
time_reply::Result::Error(Error {
|
||||
code: ErrorCode::SimulationTimeOutOfRange as i32,
|
||||
message: "the final simulation time is out of range".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => time_reply::Result::Error(Error {
|
||||
code: ErrorCode::SimulationNotStarted as i32,
|
||||
message: "the simulation was not started".to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
TimeReply {
|
||||
result: Some(reply),
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances simulation time to that of the next scheduled event, processing
|
||||
/// that event as well as all other event scheduled for the same time.
|
||||
///
|
||||
/// Processing is gated by a (possibly blocking) call to
|
||||
/// [`Clock::synchronize()`](crate::time::Clock::synchronize) on the
|
||||
/// configured simulation clock. This method blocks until all newly
|
||||
/// processed events have completed.
|
||||
pub(crate) fn step(&mut self, _request: StepRequest) -> StepReply {
|
||||
let reply = match &mut self.sim_context {
|
||||
Some((simulation, ..)) => {
|
||||
simulation.step();
|
||||
if let Some(timestamp) = monotonic_to_timestamp(simulation.time()) {
|
||||
step_reply::Result::Time(timestamp)
|
||||
} else {
|
||||
step_reply::Result::Error(Error {
|
||||
code: ErrorCode::SimulationTimeOutOfRange as i32,
|
||||
message: "the final simulation time is out of range".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => step_reply::Result::Error(Error {
|
||||
code: ErrorCode::SimulationNotStarted as i32,
|
||||
message: "the simulation was not started".to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
StepReply {
|
||||
result: Some(reply),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iteratively advances the simulation time until the specified deadline,
|
||||
/// as if by calling
|
||||
/// [`Simulation::step()`](crate::simulation::Simulation::step) repeatedly.
|
||||
///
|
||||
/// This method blocks until all events scheduled up to the specified target
|
||||
/// time have completed. The simulation time upon completion is equal to the
|
||||
/// specified target time, whether or not an event was scheduled for that
|
||||
/// time.
|
||||
pub(crate) fn step_until(&mut self, request: StepUntilRequest) -> StepUntilReply {
|
||||
let reply = move || -> Result<Timestamp, (ErrorCode, &str)> {
|
||||
let deadline = request
|
||||
.deadline
|
||||
.ok_or((ErrorCode::MissingArgument, "missing deadline argument"))?;
|
||||
|
||||
let simulation = match deadline {
|
||||
step_until_request::Deadline::Time(time) => {
|
||||
let time = timestamp_to_monotonic(time)
|
||||
.ok_or((ErrorCode::InvalidTime, "out-of-range nanosecond field"))?;
|
||||
|
||||
let (simulation, ..) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started",
|
||||
))?;
|
||||
|
||||
simulation.step_until(time).map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidTime,
|
||||
"the specified deadline lies in the past",
|
||||
)
|
||||
})?;
|
||||
|
||||
simulation
|
||||
}
|
||||
|
||||
step_until_request::Deadline::Duration(duration) => {
|
||||
let duration = to_positive_duration(duration).ok_or((
|
||||
ErrorCode::InvalidDuration,
|
||||
"the specified deadline lies in the past",
|
||||
))?;
|
||||
|
||||
let (simulation, ..) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started",
|
||||
))?;
|
||||
|
||||
simulation.step_by(duration);
|
||||
|
||||
simulation
|
||||
}
|
||||
};
|
||||
|
||||
let timestamp = monotonic_to_timestamp(simulation.time()).ok_or((
|
||||
ErrorCode::SimulationTimeOutOfRange,
|
||||
"the final simulation time is out of range",
|
||||
))?;
|
||||
|
||||
Ok(timestamp)
|
||||
}();
|
||||
|
||||
StepUntilReply {
|
||||
result: Some(match reply {
|
||||
Ok(timestamp) => step_until_reply::Result::Time(timestamp),
|
||||
Err((code, message)) => step_until_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message: message.to_string(),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules an event at a future time.
|
||||
pub(crate) fn schedule_event(&mut self, request: ScheduleEventRequest) -> ScheduleEventReply {
|
||||
let reply = move || -> Result<Option<KeyRegistryId>, (ErrorCode, String)> {
|
||||
let source_name = &request.source_name;
|
||||
let msgpack_event = &request.event;
|
||||
let with_key = request.with_key.unwrap_or_default();
|
||||
let period = request
|
||||
.period
|
||||
.map(|period| {
|
||||
to_strictly_positive_duration(period).ok_or((
|
||||
ErrorCode::InvalidDuration,
|
||||
"the specified event period is not strictly positive".to_string(),
|
||||
))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let (simulation, endpoint_registry, key_registry) =
|
||||
self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let deadline = request.deadline.ok_or((
|
||||
ErrorCode::MissingArgument,
|
||||
"missing deadline argument".to_string(),
|
||||
))?;
|
||||
|
||||
let deadline = match deadline {
|
||||
schedule_event_request::Deadline::Time(time) => timestamp_to_monotonic(time)
|
||||
.ok_or((
|
||||
ErrorCode::InvalidTime,
|
||||
"out-of-range nanosecond field".to_string(),
|
||||
))?,
|
||||
schedule_event_request::Deadline::Duration(duration) => {
|
||||
let duration = to_strictly_positive_duration(duration).ok_or((
|
||||
ErrorCode::InvalidDuration,
|
||||
"the specified scheduling deadline is not in the future".to_string(),
|
||||
))?;
|
||||
|
||||
simulation.time() + duration
|
||||
}
|
||||
};
|
||||
|
||||
let source = endpoint_registry.get_event_source_mut(source_name).ok_or((
|
||||
ErrorCode::SourceNotFound,
|
||||
"no event source is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
let (action, action_key) = match (with_key, period) {
|
||||
(false, None) => source.event(msgpack_event).map(|action| (action, None)),
|
||||
(false, Some(period)) => source
|
||||
.periodic_event(period, msgpack_event)
|
||||
.map(|action| (action, None)),
|
||||
(true, None) => source
|
||||
.keyed_event(msgpack_event)
|
||||
.map(|(action, key)| (action, Some(key))),
|
||||
(true, Some(period)) => source
|
||||
.keyed_periodic_event(period, msgpack_event)
|
||||
.map(|(action, key)| (action, Some(key))),
|
||||
}
|
||||
.map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidMessage,
|
||||
format!(
|
||||
"the event could not be deserialized as type '{}'",
|
||||
source.event_type_name()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let key_id = action_key.map(|action_key| {
|
||||
// Free stale keys from the registry.
|
||||
key_registry.remove_expired_keys(simulation.time());
|
||||
|
||||
if period.is_some() {
|
||||
key_registry.insert_eternal_key(action_key)
|
||||
} else {
|
||||
key_registry.insert_key(action_key, deadline)
|
||||
}
|
||||
});
|
||||
|
||||
simulation.process(action);
|
||||
|
||||
Ok(key_id)
|
||||
}();
|
||||
|
||||
ScheduleEventReply {
|
||||
result: Some(match reply {
|
||||
Ok(Some(key_id)) => {
|
||||
let (subkey1, subkey2) = key_id.into_raw_parts();
|
||||
schedule_event_reply::Result::Key(EventKey {
|
||||
subkey1: subkey1
|
||||
.try_into()
|
||||
.expect("action key index is too large to be serialized"),
|
||||
subkey2,
|
||||
})
|
||||
}
|
||||
Ok(None) => schedule_event_reply::Result::Empty(()),
|
||||
Err((code, message)) => schedule_event_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels a keyed event.
|
||||
pub(crate) fn cancel_event(&mut self, request: CancelEventRequest) -> CancelEventReply {
|
||||
let reply = move || -> Result<(), (ErrorCode, String)> {
|
||||
let key = request.key.ok_or((
|
||||
ErrorCode::MissingArgument,
|
||||
"missing key argument".to_string(),
|
||||
))?;
|
||||
let subkey1: usize = key
|
||||
.subkey1
|
||||
.try_into()
|
||||
.map_err(|_| (ErrorCode::InvalidKey, "invalid event key".to_string()))?;
|
||||
let subkey2 = key.subkey2;
|
||||
|
||||
let (simulation, _, key_registry) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let key_id = KeyRegistryId::from_raw_parts(subkey1, subkey2);
|
||||
|
||||
key_registry.remove_expired_keys(simulation.time());
|
||||
let key = key_registry.extract_key(key_id).ok_or((
|
||||
ErrorCode::InvalidKey,
|
||||
"invalid or expired event key".to_string(),
|
||||
))?;
|
||||
|
||||
key.cancel();
|
||||
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
CancelEventReply {
|
||||
result: Some(match reply {
|
||||
Ok(()) => cancel_event_reply::Result::Empty(()),
|
||||
Err((code, message)) => cancel_event_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcasts an event from an event source immediately, blocking until
|
||||
/// completion.
|
||||
///
|
||||
/// Simulation time remains unchanged.
|
||||
pub(crate) fn process_event(&mut self, request: ProcessEventRequest) -> ProcessEventReply {
|
||||
let reply = move || -> Result<(), (ErrorCode, String)> {
|
||||
let source_name = &request.source_name;
|
||||
let msgpack_event = &request.event;
|
||||
|
||||
let (simulation, registry, _) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let source = registry.get_event_source_mut(source_name).ok_or((
|
||||
ErrorCode::SourceNotFound,
|
||||
"no source is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
let event = source.event(msgpack_event).map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidMessage,
|
||||
format!(
|
||||
"the event could not be deserialized as type '{}'",
|
||||
source.event_type_name()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
simulation.process(event);
|
||||
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
ProcessEventReply {
|
||||
result: Some(match reply {
|
||||
Ok(()) => process_event_reply::Result::Empty(()),
|
||||
Err((code, message)) => process_event_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcasts an event from an event source immediately, blocking until
|
||||
/// completion.
|
||||
///
|
||||
/// Simulation time remains unchanged.
|
||||
pub(crate) fn process_query(&mut self, request: ProcessQueryRequest) -> ProcessQueryReply {
|
||||
let reply = move || -> Result<Vec<Vec<u8>>, (ErrorCode, String)> {
|
||||
let source_name = &request.source_name;
|
||||
let msgpack_request = &request.request;
|
||||
|
||||
let (simulation, registry, _) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let source = registry.get_query_source_mut(source_name).ok_or((
|
||||
ErrorCode::SourceNotFound,
|
||||
"no source is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
let (query, mut promise) = source.query(msgpack_request).map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidMessage,
|
||||
format!(
|
||||
"the request could not be deserialized as type '{}'",
|
||||
source.request_type_name()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
simulation.process(query);
|
||||
|
||||
let replies = promise.take_collect().ok_or((
|
||||
ErrorCode::InternalError,
|
||||
"a reply to the query was expected but none was available".to_string(),
|
||||
))?;
|
||||
|
||||
replies.map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidMessage,
|
||||
format!(
|
||||
"the reply could not be serialized as type '{}'",
|
||||
source.reply_type_name()
|
||||
),
|
||||
)
|
||||
})
|
||||
}();
|
||||
|
||||
match reply {
|
||||
Ok(replies) => ProcessQueryReply {
|
||||
replies,
|
||||
result: Some(process_query_reply::Result::Empty(())),
|
||||
},
|
||||
Err((code, message)) => ProcessQueryReply {
|
||||
replies: Vec::new(),
|
||||
result: Some(process_query_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read all events from an event sink.
|
||||
pub(crate) fn read_events(&mut self, request: ReadEventsRequest) -> ReadEventsReply {
|
||||
let reply = move || -> Result<Vec<Vec<u8>>, (ErrorCode, String)> {
|
||||
let sink_name = &request.sink_name;
|
||||
|
||||
let (_, registry, _) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let sink = registry.get_sink_mut(sink_name).ok_or((
|
||||
ErrorCode::SinkNotFound,
|
||||
"no sink is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
sink.collect().map_err(|_| {
|
||||
(
|
||||
ErrorCode::InvalidMessage,
|
||||
format!(
|
||||
"the event could not be serialized from type '{}'",
|
||||
sink.event_type_name()
|
||||
),
|
||||
)
|
||||
})
|
||||
}();
|
||||
|
||||
match reply {
|
||||
Ok(events) => ReadEventsReply {
|
||||
events,
|
||||
result: Some(read_events_reply::Result::Empty(())),
|
||||
},
|
||||
Err((code, message)) => ReadEventsReply {
|
||||
events: Vec::new(),
|
||||
result: Some(read_events_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an event sink.
|
||||
pub(crate) fn open_sink(&mut self, request: OpenSinkRequest) -> OpenSinkReply {
|
||||
let reply = move || -> Result<(), (ErrorCode, String)> {
|
||||
let sink_name = &request.sink_name;
|
||||
|
||||
let (_, registry, _) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let sink = registry.get_sink_mut(sink_name).ok_or((
|
||||
ErrorCode::SinkNotFound,
|
||||
"no sink is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
sink.open();
|
||||
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
match reply {
|
||||
Ok(()) => OpenSinkReply {
|
||||
result: Some(open_sink_reply::Result::Empty(())),
|
||||
},
|
||||
Err((code, message)) => OpenSinkReply {
|
||||
result: Some(open_sink_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes an event sink.
|
||||
pub(crate) fn close_sink(&mut self, request: CloseSinkRequest) -> CloseSinkReply {
|
||||
let reply = move || -> Result<(), (ErrorCode, String)> {
|
||||
let sink_name = &request.sink_name;
|
||||
|
||||
let (_, registry, _) = self.sim_context.as_mut().ok_or((
|
||||
ErrorCode::SimulationNotStarted,
|
||||
"the simulation was not started".to_string(),
|
||||
))?;
|
||||
|
||||
let sink = registry.get_sink_mut(sink_name).ok_or((
|
||||
ErrorCode::SinkNotFound,
|
||||
"no sink is registered with the name '{}'".to_string(),
|
||||
))?;
|
||||
|
||||
sink.close();
|
||||
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
match reply {
|
||||
Ok(()) => CloseSinkReply {
|
||||
result: Some(close_sink_reply::Result::Empty(())),
|
||||
},
|
||||
Err((code, message)) => CloseSinkReply {
|
||||
result: Some(close_sink_reply::Result::Error(Error {
|
||||
code: code as i32,
|
||||
message,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts a cast from a `MonotonicTime` to a protobuf `Timestamp`.
|
||||
///
|
||||
/// This will fail if the time is outside the protobuf-specified range for
|
||||
/// timestamps (0001-01-01 00:00:00 to 9999-12-31 23:59:59).
|
||||
fn monotonic_to_timestamp(monotonic_time: MonotonicTime) -> Option<Timestamp> {
|
||||
// Unix timestamp for 0001-01-01 00:00:00, the minimum accepted by
|
||||
// protobuf's specification for the `Timestamp` type.
|
||||
const MIN_SECS: i64 = -62135596800;
|
||||
// Unix timestamp for 9999-12-31 23:59:59, the maximum accepted by
|
||||
// protobuf's specification for the `Timestamp` type.
|
||||
const MAX_SECS: i64 = 253402300799;
|
||||
|
||||
let secs = monotonic_time.as_secs();
|
||||
if !(MIN_SECS..=MAX_SECS).contains(&secs) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Timestamp {
|
||||
seconds: secs,
|
||||
nanos: monotonic_time.subsec_nanos() as i32,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts a cast from a protobuf `Timestamp` to a `MonotonicTime`.
|
||||
///
|
||||
/// This should never fail provided that the `Timestamp` complies with the
|
||||
/// protobuf specification. It can only fail if the nanosecond part is negative
|
||||
/// or greater than 999'999'999.
|
||||
fn timestamp_to_monotonic(timestamp: Timestamp) -> Option<MonotonicTime> {
|
||||
let nanos: u32 = timestamp.nanos.try_into().ok()?;
|
||||
|
||||
MonotonicTime::new(timestamp.seconds, nanos)
|
||||
}
|
||||
|
||||
/// Attempts a cast from a protobuf `Duration` to a `std::time::Duration`.
|
||||
///
|
||||
/// If the `Duration` complies with the protobuf specification, this can only
|
||||
/// fail if the duration is negative.
|
||||
fn to_positive_duration(duration: prost_types::Duration) -> Option<Duration> {
|
||||
if duration.seconds < 0 || duration.nanos < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Duration::new(
|
||||
duration.seconds as u64,
|
||||
duration.nanos as u32,
|
||||
))
|
||||
}
|
||||
|
||||
/// Attempts a cast from a protobuf `Duration` to a strictly positive
|
||||
/// `std::time::Duration`.
|
||||
///
|
||||
/// If the `Duration` complies with the protobuf specification, this can only
|
||||
/// fail if the duration is negative or null.
|
||||
fn to_strictly_positive_duration(duration: prost_types::Duration) -> Option<Duration> {
|
||||
if duration.seconds < 0 || duration.nanos < 0 || (duration.seconds == 0 && duration.nanos == 0)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Duration::new(
|
||||
duration.seconds as u64,
|
||||
duration.nanos as u32,
|
||||
))
|
||||
}
|
146
asynchronix/src/rpc/grpc.rs
Normal file
146
asynchronix/src/rpc/grpc.rs
Normal file
@ -0,0 +1,146 @@
|
||||
//! GRPC simulation server.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
use crate::rpc::EndpointRegistry;
|
||||
use crate::simulation::SimInit;
|
||||
|
||||
use super::codegen::simulation::*;
|
||||
use super::generic_server::GenericServer;
|
||||
|
||||
/// Runs a GRPC simulation server.
|
||||
///
|
||||
/// The first argument is a closure that is called every time the simulation is
|
||||
/// started by the remote client. It must create a new `SimInit` object
|
||||
/// complemented by a registry that exposes the public event and query
|
||||
/// interface.
|
||||
pub fn run<F>(sim_gen: F, addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||
{
|
||||
// Use a single-threaded server.
|
||||
let rt = tokio::runtime::Builder::new_current_thread().build()?;
|
||||
|
||||
let sim_manager = GrpcServer::new(sim_gen);
|
||||
|
||||
rt.block_on(async move {
|
||||
Server::builder()
|
||||
.add_service(simulation_server::SimulationServer::new(sim_manager))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
struct GrpcServer<F>
|
||||
where
|
||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||
{
|
||||
inner: Mutex<GenericServer<F>>,
|
||||
}
|
||||
|
||||
impl<F> GrpcServer<F>
|
||||
where
|
||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||
{
|
||||
fn new(sim_gen: F) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(GenericServer::new(sim_gen)),
|
||||
}
|
||||
}
|
||||
|
||||
fn inner(&self) -> MutexGuard<'_, GenericServer<F>> {
|
||||
self.inner.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl<F> simulation_server::Simulation for GrpcServer<F>
|
||||
where
|
||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||
{
|
||||
async fn init(&self, request: Request<InitRequest>) -> Result<Response<InitReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().init(request)))
|
||||
}
|
||||
async fn time(&self, request: Request<TimeRequest>) -> Result<Response<TimeReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().time(request)))
|
||||
}
|
||||
async fn step(&self, request: Request<StepRequest>) -> Result<Response<StepReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().step(request)))
|
||||
}
|
||||
async fn step_until(
|
||||
&self,
|
||||
request: Request<StepUntilRequest>,
|
||||
) -> Result<Response<StepUntilReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().step_until(request)))
|
||||
}
|
||||
async fn schedule_event(
|
||||
&self,
|
||||
request: Request<ScheduleEventRequest>,
|
||||
) -> Result<Response<ScheduleEventReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().schedule_event(request)))
|
||||
}
|
||||
async fn cancel_event(
|
||||
&self,
|
||||
request: Request<CancelEventRequest>,
|
||||
) -> Result<Response<CancelEventReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().cancel_event(request)))
|
||||
}
|
||||
async fn process_event(
|
||||
&self,
|
||||
request: Request<ProcessEventRequest>,
|
||||
) -> Result<Response<ProcessEventReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().process_event(request)))
|
||||
}
|
||||
async fn process_query(
|
||||
&self,
|
||||
request: Request<ProcessQueryRequest>,
|
||||
) -> Result<Response<ProcessQueryReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().process_query(request)))
|
||||
}
|
||||
async fn read_events(
|
||||
&self,
|
||||
request: Request<ReadEventsRequest>,
|
||||
) -> Result<Response<ReadEventsReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().read_events(request)))
|
||||
}
|
||||
async fn open_sink(
|
||||
&self,
|
||||
request: Request<OpenSinkRequest>,
|
||||
) -> Result<Response<OpenSinkReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().open_sink(request)))
|
||||
}
|
||||
async fn close_sink(
|
||||
&self,
|
||||
request: Request<CloseSinkRequest>,
|
||||
) -> Result<Response<CloseSinkReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
Ok(Response::new(self.inner().close_sink(request)))
|
||||
}
|
||||
}
|
47
asynchronix/src/rpc/key_registry.rs
Normal file
47
asynchronix/src/rpc/key_registry.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::time::{ActionKey, MonotonicTime};
|
||||
use crate::util::indexed_priority_queue::{IndexedPriorityQueue, InsertKey};
|
||||
|
||||
pub(crate) type KeyRegistryId = InsertKey;
|
||||
|
||||
/// A collection of `ActionKey`s indexed by a unique identifier.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct KeyRegistry {
|
||||
keys: IndexedPriorityQueue<MonotonicTime, ActionKey>,
|
||||
}
|
||||
|
||||
impl KeyRegistry {
|
||||
/// Inserts an `ActionKey` into the registry.
|
||||
///
|
||||
/// The provided expiration deadline is the latest time at which the key may
|
||||
/// still be active.
|
||||
pub(crate) fn insert_key(
|
||||
&mut self,
|
||||
action_key: ActionKey,
|
||||
expiration: MonotonicTime,
|
||||
) -> KeyRegistryId {
|
||||
self.keys.insert(expiration, action_key)
|
||||
}
|
||||
|
||||
/// Inserts a non-expiring `ActionKey` into the registry.
|
||||
pub(crate) fn insert_eternal_key(&mut self, action_key: ActionKey) -> KeyRegistryId {
|
||||
self.keys.insert(MonotonicTime::MAX, action_key)
|
||||
}
|
||||
|
||||
/// Removes an `ActionKey` from the registry and returns it.
|
||||
///
|
||||
/// Returns `None` if the key was not found in the registry.
|
||||
pub(crate) fn extract_key(&mut self, key_id: KeyRegistryId) -> Option<ActionKey> {
|
||||
self.keys.extract(key_id).map(|(_, key)| key)
|
||||
}
|
||||
|
||||
/// Remove keys with an expiration deadline strictly predating the argument.
|
||||
pub(crate) fn remove_expired_keys(&mut self, now: MonotonicTime) {
|
||||
while let Some(expiration) = self.keys.peek_key() {
|
||||
if *expiration >= now {
|
||||
return;
|
||||
}
|
||||
|
||||
self.keys.pull();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user