mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
use customized rpc implementation, remove Tarpc & Tonic (#348)
This patch removes Tarpc & Tonic GRPC and implements a customized rpc framework, which can be used by peer rpc and cli interface. web config server can also use this rpc framework. moreover, rewrite the public server logic, use ospf route to implement public server based networking. this make public server mesh possible.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
package cli;
|
||||
|
||||
message Status {
|
||||
int32 code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message PeerConnStats {
|
||||
uint64 rx_bytes = 1;
|
||||
uint64 tx_bytes = 2;
|
||||
|
||||
uint64 rx_packets = 3;
|
||||
uint64 tx_packets = 4;
|
||||
|
||||
uint64 latency_us = 5;
|
||||
}
|
||||
|
||||
message PeerConnInfo {
|
||||
string conn_id = 1;
|
||||
uint32 my_peer_id = 2;
|
||||
uint32 peer_id = 3;
|
||||
repeated string features = 4;
|
||||
common.TunnelInfo tunnel = 5;
|
||||
PeerConnStats stats = 6;
|
||||
float loss_rate = 7;
|
||||
bool is_client = 8;
|
||||
string network_name = 9;
|
||||
}
|
||||
|
||||
message PeerInfo {
|
||||
uint32 peer_id = 1;
|
||||
repeated PeerConnInfo conns = 2;
|
||||
}
|
||||
|
||||
message ListPeerRequest {}
|
||||
|
||||
message ListPeerResponse {
|
||||
repeated PeerInfo peer_infos = 1;
|
||||
NodeInfo my_info = 2;
|
||||
}
|
||||
|
||||
message Route {
|
||||
uint32 peer_id = 1;
|
||||
string ipv4_addr = 2;
|
||||
uint32 next_hop_peer_id = 3;
|
||||
int32 cost = 4;
|
||||
repeated string proxy_cidrs = 5;
|
||||
string hostname = 6;
|
||||
common.StunInfo stun_info = 7;
|
||||
string inst_id = 8;
|
||||
string version = 9;
|
||||
}
|
||||
|
||||
message NodeInfo {
|
||||
uint32 peer_id = 1;
|
||||
string ipv4_addr = 2;
|
||||
repeated string proxy_cidrs = 3;
|
||||
string hostname = 4;
|
||||
common.StunInfo stun_info = 5;
|
||||
string inst_id = 6;
|
||||
repeated string listeners = 7;
|
||||
string config = 8;
|
||||
string version = 9;
|
||||
}
|
||||
|
||||
message ShowNodeInfoRequest {}
|
||||
|
||||
message ShowNodeInfoResponse { NodeInfo node_info = 1; }
|
||||
|
||||
message ListRouteRequest {}
|
||||
|
||||
message ListRouteResponse { repeated Route routes = 1; }
|
||||
|
||||
message DumpRouteRequest {}
|
||||
|
||||
message DumpRouteResponse { string result = 1; }
|
||||
|
||||
message ListForeignNetworkRequest {}
|
||||
|
||||
message ForeignNetworkEntryPb { repeated PeerInfo peers = 1; }
|
||||
|
||||
message ListForeignNetworkResponse {
|
||||
map<string, ForeignNetworkEntryPb> foreign_networks = 1;
|
||||
}
|
||||
|
||||
service PeerManageRpc {
|
||||
rpc ListPeer(ListPeerRequest) returns (ListPeerResponse);
|
||||
rpc ListRoute(ListRouteRequest) returns (ListRouteResponse);
|
||||
rpc DumpRoute(DumpRouteRequest) returns (DumpRouteResponse);
|
||||
rpc ListForeignNetwork(ListForeignNetworkRequest)
|
||||
returns (ListForeignNetworkResponse);
|
||||
rpc ShowNodeInfo(ShowNodeInfoRequest) returns (ShowNodeInfoResponse);
|
||||
}
|
||||
|
||||
enum ConnectorStatus {
|
||||
CONNECTED = 0;
|
||||
DISCONNECTED = 1;
|
||||
CONNECTING = 2;
|
||||
}
|
||||
|
||||
message Connector {
|
||||
common.Url url = 1;
|
||||
ConnectorStatus status = 2;
|
||||
}
|
||||
|
||||
message ListConnectorRequest {}
|
||||
|
||||
message ListConnectorResponse { repeated Connector connectors = 1; }
|
||||
|
||||
enum ConnectorManageAction {
|
||||
ADD = 0;
|
||||
REMOVE = 1;
|
||||
}
|
||||
|
||||
message ManageConnectorRequest {
|
||||
ConnectorManageAction action = 1;
|
||||
common.Url url = 2;
|
||||
}
|
||||
|
||||
message ManageConnectorResponse {}
|
||||
|
||||
service ConnectorManageRpc {
|
||||
rpc ListConnector(ListConnectorRequest) returns (ListConnectorResponse);
|
||||
rpc ManageConnector(ManageConnectorRequest) returns (ManageConnectorResponse);
|
||||
}
|
||||
|
||||
message VpnPortalInfo {
|
||||
string vpn_type = 1;
|
||||
string client_config = 2;
|
||||
repeated string connected_clients = 3;
|
||||
}
|
||||
|
||||
message GetVpnPortalInfoRequest {}
|
||||
message GetVpnPortalInfoResponse { VpnPortalInfo vpn_portal_info = 1; }
|
||||
|
||||
service VpnPortalRpc {
|
||||
rpc GetVpnPortalInfo(GetVpnPortalInfoRequest)
|
||||
returns (GetVpnPortalInfoResponse);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/cli.rs"));
|
||||
@@ -0,0 +1,92 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "error.proto";
|
||||
|
||||
package common;
|
||||
|
||||
message RpcDescriptor {
|
||||
// allow same service registered multiple times in different domain
|
||||
string domain_name = 1;
|
||||
|
||||
string proto_name = 2;
|
||||
string service_name = 3;
|
||||
uint32 method_index = 4;
|
||||
}
|
||||
|
||||
message RpcRequest {
|
||||
RpcDescriptor descriptor = 1;
|
||||
|
||||
bytes request = 2;
|
||||
int32 timeout_ms = 3;
|
||||
}
|
||||
|
||||
message RpcResponse {
|
||||
bytes response = 1;
|
||||
error.Error error = 2;
|
||||
|
||||
uint64 runtime_us = 3;
|
||||
}
|
||||
|
||||
message RpcPacket {
|
||||
uint32 from_peer = 1;
|
||||
uint32 to_peer = 2;
|
||||
int64 transaction_id = 3;
|
||||
|
||||
RpcDescriptor descriptor = 4;
|
||||
bytes body = 5;
|
||||
bool is_request = 6;
|
||||
|
||||
uint32 total_pieces = 7;
|
||||
uint32 piece_idx = 8;
|
||||
|
||||
int32 trace_id = 9;
|
||||
}
|
||||
|
||||
message UUID {
|
||||
uint64 high = 1;
|
||||
uint64 low = 2;
|
||||
}
|
||||
|
||||
enum NatType {
|
||||
// has NAT; but own a single public IP, port is not changed
|
||||
Unknown = 0;
|
||||
OpenInternet = 1;
|
||||
NoPAT = 2;
|
||||
FullCone = 3;
|
||||
Restricted = 4;
|
||||
PortRestricted = 5;
|
||||
Symmetric = 6;
|
||||
SymUdpFirewall = 7;
|
||||
}
|
||||
|
||||
message Ipv4Addr { uint32 addr = 1; }
|
||||
|
||||
message Ipv6Addr {
|
||||
uint64 high = 1;
|
||||
uint64 low = 2;
|
||||
}
|
||||
|
||||
message Url { string url = 1; }
|
||||
|
||||
message SocketAddr {
|
||||
oneof ip {
|
||||
Ipv4Addr ipv4 = 1;
|
||||
Ipv6Addr ipv6 = 2;
|
||||
};
|
||||
uint32 port = 3;
|
||||
}
|
||||
|
||||
message TunnelInfo {
|
||||
string tunnel_type = 1;
|
||||
common.Url local_addr = 2;
|
||||
common.Url remote_addr = 3;
|
||||
}
|
||||
|
||||
message StunInfo {
|
||||
NatType udp_nat_type = 1;
|
||||
NatType tcp_nat_type = 2;
|
||||
int64 last_update_time = 3;
|
||||
repeated string public_ip = 4;
|
||||
uint32 min_port = 5;
|
||||
uint32 max_port = 6;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/common.rs"));
|
||||
|
||||
impl From<uuid::Uuid> for Uuid {
|
||||
fn from(uuid: uuid::Uuid) -> Self {
|
||||
let (high, low) = uuid.as_u64_pair();
|
||||
Uuid { low, high }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for uuid::Uuid {
|
||||
fn from(uuid: Uuid) -> Self {
|
||||
uuid::Uuid::from_u64_pair(uuid.high, uuid.low)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Uuid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", uuid::Uuid::from(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv4Addr> for Ipv4Addr {
|
||||
fn from(value: std::net::Ipv4Addr) -> Self {
|
||||
Self {
|
||||
addr: u32::from_be_bytes(value.octets()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for std::net::Ipv4Addr {
|
||||
fn from(value: Ipv4Addr) -> Self {
|
||||
std::net::Ipv4Addr::from(value.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Ipv4Addr {
|
||||
fn to_string(&self) -> String {
|
||||
std::net::Ipv4Addr::from(self.addr).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv6Addr> for Ipv6Addr {
|
||||
fn from(value: std::net::Ipv6Addr) -> Self {
|
||||
let b = value.octets();
|
||||
Self {
|
||||
low: u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
|
||||
high: u64::from_be_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for std::net::Ipv6Addr {
|
||||
fn from(value: Ipv6Addr) -> Self {
|
||||
let low = value.low.to_be_bytes();
|
||||
let high = value.high.to_be_bytes();
|
||||
std::net::Ipv6Addr::from([
|
||||
low[0], low[1], low[2], low[3], low[4], low[5], low[6], low[7], high[0], high[1],
|
||||
high[2], high[3], high[4], high[5], high[6], high[7],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Ipv6Addr {
|
||||
fn to_string(&self) -> String {
|
||||
std::net::Ipv6Addr::from(self.clone()).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for Url {
|
||||
fn from(value: url::Url) -> Self {
|
||||
Url {
|
||||
url: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for url::Url {
|
||||
fn from(value: Url) -> Self {
|
||||
url::Url::parse(&value.url).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = url::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Url {
|
||||
url: s.parse::<url::Url>()?.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Url {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.url)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::SocketAddr> for SocketAddr {
|
||||
fn from(value: std::net::SocketAddr) -> Self {
|
||||
match value {
|
||||
std::net::SocketAddr::V4(v4) => SocketAddr {
|
||||
ip: Some(socket_addr::Ip::Ipv4(v4.ip().clone().into())),
|
||||
port: v4.port() as u32,
|
||||
},
|
||||
std::net::SocketAddr::V6(v6) => SocketAddr {
|
||||
ip: Some(socket_addr::Ip::Ipv6(v6.ip().clone().into())),
|
||||
port: v6.port() as u32,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddr> for std::net::SocketAddr {
|
||||
fn from(value: SocketAddr) -> Self {
|
||||
match value.ip.unwrap() {
|
||||
socket_addr::Ip::Ipv4(ip) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
|
||||
std::net::Ipv4Addr::from(ip),
|
||||
value.port as u16,
|
||||
)),
|
||||
socket_addr::Ip::Ipv6(ip) => std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
|
||||
std::net::Ipv6Addr::from(ip),
|
||||
value.port as u16,
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
package error;
|
||||
|
||||
message OtherError { string error_message = 1; }
|
||||
|
||||
message InvalidMethodIndex {
|
||||
string service_name = 1;
|
||||
uint32 method_index = 2;
|
||||
}
|
||||
|
||||
message InvalidService { string service_name = 1; }
|
||||
|
||||
message ProstDecodeError {}
|
||||
|
||||
message ProstEncodeError {}
|
||||
|
||||
message ExecuteError { string error_message = 1; }
|
||||
|
||||
message MalformatRpcPacket { string error_message = 1; }
|
||||
|
||||
message Timeout { string error_message = 1; }
|
||||
|
||||
message Error {
|
||||
oneof error {
|
||||
OtherError other_error = 1;
|
||||
InvalidMethodIndex invalid_method_index = 2;
|
||||
InvalidService invalid_service = 3;
|
||||
ProstDecodeError prost_decode_error = 4;
|
||||
ProstEncodeError prost_encode_error = 5;
|
||||
ExecuteError execute_error = 6;
|
||||
MalformatRpcPacket malformat_rpc_packet = 7;
|
||||
Timeout timeout = 8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use prost::DecodeError;
|
||||
|
||||
use super::rpc_types;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/error.rs"));
|
||||
|
||||
impl From<&rpc_types::error::Error> for Error {
|
||||
fn from(e: &rpc_types::error::Error) -> Self {
|
||||
use super::error::error::Error as ProtoError;
|
||||
match e {
|
||||
rpc_types::error::Error::ExecutionError(e) => Self {
|
||||
error: Some(ProtoError::ExecuteError(ExecuteError {
|
||||
error_message: e.to_string(),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::DecodeError(_) => Self {
|
||||
error: Some(ProtoError::ProstDecodeError(ProstDecodeError {})),
|
||||
},
|
||||
rpc_types::error::Error::EncodeError(_) => Self {
|
||||
error: Some(ProtoError::ProstEncodeError(ProstEncodeError {})),
|
||||
},
|
||||
rpc_types::error::Error::InvalidMethodIndex(m, s) => Self {
|
||||
error: Some(ProtoError::InvalidMethodIndex(InvalidMethodIndex {
|
||||
method_index: *m as u32,
|
||||
service_name: s.to_string(),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::InvalidServiceKey(s, _) => Self {
|
||||
error: Some(ProtoError::InvalidService(InvalidService {
|
||||
service_name: s.to_string(),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::MalformatRpcPacket(e) => Self {
|
||||
error: Some(ProtoError::MalformatRpcPacket(MalformatRpcPacket {
|
||||
error_message: e.to_string(),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::Timeout(e) => Self {
|
||||
error: Some(ProtoError::Timeout(Timeout {
|
||||
error_message: e.to_string(),
|
||||
})),
|
||||
},
|
||||
#[allow(unreachable_patterns)]
|
||||
e => Self {
|
||||
error: Some(ProtoError::OtherError(OtherError {
|
||||
error_message: e.to_string(),
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Error> for rpc_types::error::Error {
|
||||
fn from(e: &Error) -> Self {
|
||||
use super::error::error::Error as ProtoError;
|
||||
match &e.error {
|
||||
Some(ProtoError::ExecuteError(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
Some(ProtoError::ProstDecodeError(_)) => {
|
||||
Self::DecodeError(DecodeError::new("decode error"))
|
||||
}
|
||||
Some(ProtoError::ProstEncodeError(_)) => {
|
||||
Self::DecodeError(DecodeError::new("encode error"))
|
||||
}
|
||||
Some(ProtoError::InvalidMethodIndex(e)) => {
|
||||
Self::InvalidMethodIndex(e.method_index as u8, e.service_name.clone())
|
||||
}
|
||||
Some(ProtoError::InvalidService(e)) => {
|
||||
Self::InvalidServiceKey(e.service_name.clone(), "".to_string())
|
||||
}
|
||||
Some(ProtoError::MalformatRpcPacket(e)) => {
|
||||
Self::MalformatRpcPacket(e.error_message.clone())
|
||||
}
|
||||
Some(ProtoError::Timeout(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
Some(ProtoError::OtherError(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
None => Self::ExecutionError(anyhow::anyhow!("unknown error {:?}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod rpc_impl;
|
||||
pub mod rpc_types;
|
||||
|
||||
pub mod cli;
|
||||
pub mod common;
|
||||
pub mod error;
|
||||
pub mod peer_rpc;
|
||||
|
||||
pub mod tests;
|
||||
@@ -0,0 +1,129 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "common.proto";
|
||||
|
||||
package peer_rpc;
|
||||
|
||||
message RoutePeerInfo {
|
||||
// means next hop in route table.
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
uint32 cost = 3;
|
||||
optional common.Ipv4Addr ipv4_addr = 4;
|
||||
repeated string proxy_cidrs = 5;
|
||||
optional string hostname = 6;
|
||||
common.NatType udp_stun_info = 7;
|
||||
google.protobuf.Timestamp last_update = 8;
|
||||
uint32 version = 9;
|
||||
}
|
||||
|
||||
message PeerIdVersion {
|
||||
uint32 peer_id = 1;
|
||||
uint32 version = 2;
|
||||
}
|
||||
|
||||
message RouteConnBitmap {
|
||||
repeated PeerIdVersion peer_ids = 1;
|
||||
bytes bitmap = 2;
|
||||
}
|
||||
|
||||
message RoutePeerInfos { repeated RoutePeerInfo items = 1; }
|
||||
|
||||
message SyncRouteInfoRequest {
|
||||
uint32 my_peer_id = 1;
|
||||
uint64 my_session_id = 2;
|
||||
bool is_initiator = 3;
|
||||
RoutePeerInfos peer_infos = 4;
|
||||
RouteConnBitmap conn_bitmap = 5;
|
||||
}
|
||||
|
||||
enum SyncRouteInfoError {
|
||||
DuplicatePeerId = 0;
|
||||
Stopped = 1;
|
||||
}
|
||||
|
||||
message SyncRouteInfoResponse {
|
||||
bool is_initiator = 1;
|
||||
uint64 session_id = 2;
|
||||
optional SyncRouteInfoError error = 3;
|
||||
}
|
||||
|
||||
service OspfRouteRpc {
|
||||
// Generates a "hello" greeting based on the supplied info.
|
||||
rpc SyncRouteInfo(SyncRouteInfoRequest) returns (SyncRouteInfoResponse);
|
||||
}
|
||||
|
||||
message GetIpListRequest {}
|
||||
|
||||
message GetIpListResponse {
|
||||
common.Ipv4Addr public_ipv4 = 1;
|
||||
repeated common.Ipv4Addr interface_ipv4s = 2;
|
||||
common.Ipv6Addr public_ipv6 = 3;
|
||||
repeated common.Ipv6Addr interface_ipv6s = 4;
|
||||
repeated common.Url listeners = 5;
|
||||
}
|
||||
|
||||
service DirectConnectorRpc {
|
||||
rpc GetIpList(GetIpListRequest) returns (GetIpListResponse);
|
||||
}
|
||||
|
||||
message TryPunchHoleRequest { common.SocketAddr local_mapped_addr = 1; }
|
||||
|
||||
message TryPunchHoleResponse { common.SocketAddr remote_mapped_addr = 1; }
|
||||
|
||||
message TryPunchSymmetricRequest {
|
||||
common.SocketAddr listener_addr = 1;
|
||||
uint32 port = 2;
|
||||
repeated common.Ipv4Addr public_ips = 3;
|
||||
uint32 min_port = 4;
|
||||
uint32 max_port = 5;
|
||||
uint32 transaction_id = 6;
|
||||
uint32 round = 7;
|
||||
uint32 last_port_index = 8;
|
||||
}
|
||||
|
||||
message TryPunchSymmetricResponse { uint32 last_port_index = 1; }
|
||||
|
||||
service UdpHolePunchRpc {
|
||||
rpc TryPunchHole(TryPunchHoleRequest) returns (TryPunchHoleResponse);
|
||||
rpc TryPunchSymmetric(TryPunchSymmetricRequest)
|
||||
returns (TryPunchSymmetricResponse);
|
||||
}
|
||||
|
||||
message DirectConnectedPeerInfo { int32 latency_ms = 1; }
|
||||
|
||||
message PeerInfoForGlobalMap {
|
||||
map<uint32, DirectConnectedPeerInfo> direct_peers = 1;
|
||||
}
|
||||
|
||||
message ReportPeersRequest {
|
||||
uint32 my_peer_id = 1;
|
||||
PeerInfoForGlobalMap peer_infos = 2;
|
||||
}
|
||||
|
||||
message ReportPeersResponse {}
|
||||
|
||||
message GlobalPeerMap { map<uint32, PeerInfoForGlobalMap> map = 1; }
|
||||
|
||||
message GetGlobalPeerMapRequest { uint64 digest = 1; }
|
||||
|
||||
message GetGlobalPeerMapResponse {
|
||||
map<uint32, PeerInfoForGlobalMap> global_peer_map = 1;
|
||||
optional uint64 digest = 2;
|
||||
}
|
||||
|
||||
service PeerCenterRpc {
|
||||
rpc ReportPeers(ReportPeersRequest) returns (ReportPeersResponse);
|
||||
rpc GetGlobalPeerMap(GetGlobalPeerMapRequest)
|
||||
returns (GetGlobalPeerMapResponse);
|
||||
}
|
||||
|
||||
message HandshakeRequest {
|
||||
uint32 magic = 1;
|
||||
uint32 my_peer_id = 2;
|
||||
uint32 version = 3;
|
||||
repeated string features = 4;
|
||||
string network_name = 5;
|
||||
bytes network_secret_digrest = 6;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/peer_rpc.rs"));
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "rpc_build"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
heck = "0.5"
|
||||
prost-build = "0.13"
|
||||
@@ -0,0 +1,383 @@
|
||||
extern crate heck;
|
||||
extern crate prost_build;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
const NAMESPACE: &str = "crate::proto::rpc_types";
|
||||
|
||||
/// The service generator to be used with `prost-build` to generate RPC implementations for
|
||||
/// `prost-simple-rpc`.
|
||||
///
|
||||
/// See the crate-level documentation for more info.
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServiceGenerator {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl ServiceGenerator {
|
||||
/// Create a new `ServiceGenerator` instance with the default options set.
|
||||
pub fn new() -> ServiceGenerator {
|
||||
ServiceGenerator { _private: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl prost_build::ServiceGenerator for ServiceGenerator {
|
||||
fn generate(&mut self, service: prost_build::Service, mut buf: &mut String) {
|
||||
use std::fmt::Write;
|
||||
|
||||
let descriptor_name = format!("{}Descriptor", service.name);
|
||||
let server_name = format!("{}Server", service.name);
|
||||
let client_name = format!("{}Client", service.name);
|
||||
let method_descriptor_name = format!("{}MethodDescriptor", service.name);
|
||||
|
||||
let mut trait_methods = String::new();
|
||||
let mut enum_methods = String::new();
|
||||
let mut list_enum_methods = String::new();
|
||||
let mut client_methods = String::new();
|
||||
let mut client_own_methods = String::new();
|
||||
let mut match_name_methods = String::new();
|
||||
let mut match_proto_name_methods = String::new();
|
||||
let mut match_input_type_methods = String::new();
|
||||
let mut match_input_proto_type_methods = String::new();
|
||||
let mut match_output_type_methods = String::new();
|
||||
let mut match_output_proto_type_methods = String::new();
|
||||
let mut match_handle_methods = String::new();
|
||||
|
||||
let mut match_method_try_from = String::new();
|
||||
|
||||
for (idx, method) in service.methods.iter().enumerate() {
|
||||
assert!(
|
||||
!method.client_streaming,
|
||||
"Client streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
assert!(
|
||||
!method.server_streaming,
|
||||
"Server streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
|
||||
ServiceGenerator::write_comments(&mut trait_methods, 4, &method.comments).unwrap();
|
||||
writeln!(
|
||||
trait_methods,
|
||||
r#" async fn {name}(&self, ctrl: Self::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}>;"#,
|
||||
name = method.name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ServiceGenerator::write_comments(&mut enum_methods, 4, &method.comments).unwrap();
|
||||
writeln!(
|
||||
enum_methods,
|
||||
" {name} = {index},",
|
||||
name = method.proto_name,
|
||||
index = format!("{}", idx + 1)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
match_method_try_from,
|
||||
" {index} => Ok({service_name}MethodDescriptor::{name}),",
|
||||
service_name = service.name,
|
||||
name = method.proto_name,
|
||||
index = format!("{}", idx + 1),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
list_enum_methods,
|
||||
" {service_name}MethodDescriptor::{name},",
|
||||
service_name = service.name,
|
||||
name = method.proto_name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
client_methods,
|
||||
r#" async fn {name}(&self, ctrl: H::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}> {{
|
||||
{client_name}::{name}_inner(self.0.clone(), ctrl, input).await
|
||||
}}"#,
|
||||
name = method.name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
client_name = format!("{}Client", service.name),
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
client_own_methods,
|
||||
r#" async fn {name}_inner(handler: H, ctrl: H::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}> {{
|
||||
{namespace}::__rt::call_method(handler, ctrl, {method_descriptor_name}::{proto_name}, input).await
|
||||
}}"#,
|
||||
name = method.name,
|
||||
method_descriptor_name = method_descriptor_name,
|
||||
proto_name = method.proto_name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
namespace = NAMESPACE,
|
||||
).unwrap();
|
||||
|
||||
let case = format!(
|
||||
" {service_name}MethodDescriptor::{proto_name} => ",
|
||||
service_name = service.name,
|
||||
proto_name = method.proto_name
|
||||
);
|
||||
|
||||
writeln!(match_name_methods, "{}{:?},", case, method.name).unwrap();
|
||||
writeln!(match_proto_name_methods, "{}{:?},", case, method.proto_name).unwrap();
|
||||
writeln!(
|
||||
match_input_type_methods,
|
||||
"{}::std::any::TypeId::of::<{}>(),",
|
||||
case, method.input_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_input_proto_type_methods,
|
||||
"{}{:?},",
|
||||
case, method.input_proto_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_output_type_methods,
|
||||
"{}::std::any::TypeId::of::<{}>(),",
|
||||
case, method.output_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_output_proto_type_methods,
|
||||
"{}{:?},",
|
||||
case, method.output_proto_type
|
||||
)
|
||||
.unwrap();
|
||||
write!(
|
||||
match_handle_methods,
|
||||
r#"{} {{
|
||||
let decoded: {input_type} = {namespace}::__rt::decode(input)?;
|
||||
let ret = service.{name}(ctrl, decoded).await?;
|
||||
{namespace}::__rt::encode(ret)
|
||||
}}
|
||||
"#,
|
||||
case,
|
||||
input_type = method.input_type,
|
||||
name = method.name,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ServiceGenerator::write_comments(&mut buf, 0, &service.comments).unwrap();
|
||||
write!(
|
||||
buf,
|
||||
r#"
|
||||
#[async_trait::async_trait]
|
||||
#[auto_impl::auto_impl(&, Arc, Box)]
|
||||
pub trait {name} {{
|
||||
type Controller: {namespace}::controller::Controller;
|
||||
|
||||
{trait_methods}
|
||||
}}
|
||||
|
||||
/// A service descriptor for a `{name}`.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Default)]
|
||||
pub struct {descriptor_name};
|
||||
|
||||
/// Methods available on a `{name}`.
|
||||
///
|
||||
/// This can be used as a key when routing requests for servers/clients of a `{name}`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum {method_descriptor_name} {{
|
||||
{enum_methods}
|
||||
}}
|
||||
|
||||
impl std::convert::TryFrom<u8> for {method_descriptor_name} {{
|
||||
type Error = {namespace}::error::Error;
|
||||
fn try_from(value: u8) -> {namespace}::error::Result<Self> {{
|
||||
match value {{
|
||||
{match_method_try_from}
|
||||
_ => Err({namespace}::error::Error::InvalidMethodIndex(value, "{name}".to_string())),
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
/// A client for a `{name}`.
|
||||
///
|
||||
/// This implements the `{name}` trait by dispatching all method calls to the supplied `Handler`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct {client_name}<H>(H) where H: {namespace}::handler::Handler;
|
||||
|
||||
impl<H> {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
/// Creates a new client instance that delegates all method calls to the supplied handler.
|
||||
pub fn new(handler: H) -> {client_name}<H> {{
|
||||
{client_name}(handler)
|
||||
}}
|
||||
}}
|
||||
|
||||
impl<H> {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
{client_own_methods}
|
||||
}}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<H> {name} for {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
type Controller = H::Controller;
|
||||
|
||||
{client_methods}
|
||||
}}
|
||||
|
||||
pub struct {client_name}Factory<C: {namespace}::controller::Controller>(std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: {namespace}::controller::Controller> Clone for {client_name}Factory<C> {{
|
||||
fn clone(&self) -> Self {{
|
||||
Self(std::marker::PhantomData)
|
||||
}}
|
||||
}}
|
||||
|
||||
impl<C> {namespace}::__rt::RpcClientFactory for {client_name}Factory<C> where C: {namespace}::controller::Controller {{
|
||||
type Descriptor = {descriptor_name};
|
||||
type ClientImpl = Box<dyn {name}<Controller = C> + Send + 'static>;
|
||||
type Controller = C;
|
||||
|
||||
fn new(handler: impl {namespace}::handler::Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>) -> Self::ClientImpl {{
|
||||
Box::new({client_name}::new(handler))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// A server for a `{name}`.
|
||||
///
|
||||
/// This implements the `Server` trait by handling requests and dispatch them to methods on the
|
||||
/// supplied `{name}`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct {server_name}<A>(A) where A: {name} + Clone + Send + 'static;
|
||||
|
||||
impl<A> {server_name}<A> where A: {name} + Clone + Send + 'static {{
|
||||
/// Creates a new server instance that dispatches all calls to the supplied service.
|
||||
pub fn new(service: A) -> {server_name}<A> {{
|
||||
{server_name}(service)
|
||||
}}
|
||||
|
||||
async fn call_inner(
|
||||
service: A,
|
||||
method: {method_descriptor_name},
|
||||
ctrl: A::Controller,
|
||||
input: ::bytes::Bytes)
|
||||
-> {namespace}::error::Result<::bytes::Bytes> {{
|
||||
match method {{
|
||||
{match_handle_methods}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
impl {namespace}::descriptor::ServiceDescriptor for {descriptor_name} {{
|
||||
type Method = {method_descriptor_name};
|
||||
fn name(&self) -> &'static str {{ {name:?} }}
|
||||
fn proto_name(&self) -> &'static str {{ {proto_name:?} }}
|
||||
fn package(&self) -> &'static str {{ {package:?} }}
|
||||
fn methods(&self) -> &'static [Self::Method] {{
|
||||
&[ {list_enum_methods} ]
|
||||
}}
|
||||
}}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<A> {namespace}::handler::Handler for {server_name}<A>
|
||||
where
|
||||
A: {name} + Clone + Send + Sync + 'static {{
|
||||
type Descriptor = {descriptor_name};
|
||||
type Controller = A::Controller;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: A::Controller,
|
||||
method: {method_descriptor_name},
|
||||
input: ::bytes::Bytes)
|
||||
-> {namespace}::error::Result<::bytes::Bytes> {{
|
||||
{server_name}::call_inner(self.0.clone(), method, ctrl, input).await
|
||||
}}
|
||||
}}
|
||||
|
||||
impl {namespace}::descriptor::MethodDescriptor for {method_descriptor_name} {{
|
||||
fn name(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_name_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn proto_name(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_proto_name_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn input_type(&self) -> ::std::any::TypeId {{
|
||||
match *self {{
|
||||
{match_input_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn input_proto_type(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_input_proto_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn output_type(&self) -> ::std::any::TypeId {{
|
||||
match *self {{
|
||||
{match_output_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn output_proto_type(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_output_proto_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn index(&self) -> u8 {{
|
||||
*self as u8
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
name = service.name,
|
||||
descriptor_name = descriptor_name,
|
||||
server_name = server_name,
|
||||
client_name = client_name,
|
||||
method_descriptor_name = method_descriptor_name,
|
||||
proto_name = service.proto_name,
|
||||
package = service.package,
|
||||
trait_methods = trait_methods,
|
||||
enum_methods = enum_methods,
|
||||
list_enum_methods = list_enum_methods,
|
||||
client_own_methods = client_own_methods,
|
||||
client_methods = client_methods,
|
||||
match_name_methods = match_name_methods,
|
||||
match_proto_name_methods = match_proto_name_methods,
|
||||
match_input_type_methods = match_input_type_methods,
|
||||
match_input_proto_type_methods = match_input_proto_type_methods,
|
||||
match_output_type_methods = match_output_type_methods,
|
||||
match_output_proto_type_methods = match_output_proto_type_methods,
|
||||
match_handle_methods = match_handle_methods,
|
||||
namespace = NAMESPACE,
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceGenerator {
|
||||
fn write_comments<W>(
|
||||
mut write: W,
|
||||
indent: usize,
|
||||
comments: &prost_build::Comments,
|
||||
) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
for comment in &comments.leading {
|
||||
for line in comment.lines().filter(|s| !s.is_empty()) {
|
||||
writeln!(write, "{}///{}", " ".repeat(indent), line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use prost::Message;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::time::timeout;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use crate::common::PeerId;
|
||||
use crate::defer;
|
||||
use crate::proto::common::{RpcDescriptor, RpcPacket, RpcRequest, RpcResponse};
|
||||
use crate::proto::rpc_impl::packet::build_rpc_packet;
|
||||
use crate::proto::rpc_types::controller::Controller;
|
||||
use crate::proto::rpc_types::descriptor::MethodDescriptor;
|
||||
use crate::proto::rpc_types::{
|
||||
__rt::RpcClientFactory, descriptor::ServiceDescriptor, handler::Handler,
|
||||
};
|
||||
|
||||
use crate::proto::rpc_types::error::Result;
|
||||
use crate::tunnel::mpsc::{MpscTunnel, MpscTunnelSender};
|
||||
use crate::tunnel::packet_def::ZCPacket;
|
||||
use crate::tunnel::ring::create_ring_tunnel_pair;
|
||||
use crate::tunnel::{Tunnel, TunnelError, ZCPacketStream};
|
||||
|
||||
use super::packet::PacketMerger;
|
||||
use super::{RpcTransactId, Transport};
|
||||
|
||||
static CUR_TID: once_cell::sync::Lazy<atomic_shim::AtomicI64> =
|
||||
once_cell::sync::Lazy::new(|| atomic_shim::AtomicI64::new(rand::random()));
|
||||
|
||||
type RpcPacketSender = mpsc::UnboundedSender<RpcPacket>;
|
||||
type RpcPacketReceiver = mpsc::UnboundedReceiver<RpcPacket>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct InflightRequestKey {
|
||||
from_peer_id: PeerId,
|
||||
to_peer_id: PeerId,
|
||||
transaction_id: RpcTransactId,
|
||||
}
|
||||
|
||||
struct InflightRequest {
|
||||
sender: RpcPacketSender,
|
||||
merger: PacketMerger,
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
type InflightRequestTable = Arc<DashMap<InflightRequestKey, InflightRequest>>;
|
||||
|
||||
pub struct Client {
|
||||
mpsc: Mutex<MpscTunnel<Box<dyn Tunnel>>>,
|
||||
transport: Mutex<Transport>,
|
||||
inflight_requests: InflightRequestTable,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
let (ring_a, ring_b) = create_ring_tunnel_pair();
|
||||
Self {
|
||||
mpsc: Mutex::new(MpscTunnel::new(ring_a)),
|
||||
transport: Mutex::new(MpscTunnel::new(ring_b)),
|
||||
inflight_requests: Arc::new(DashMap::new()),
|
||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_transport_sink(&self) -> MpscTunnelSender {
|
||||
self.transport.lock().unwrap().get_sink()
|
||||
}
|
||||
|
||||
pub fn get_transport_stream(&self) -> Pin<Box<dyn ZCPacketStream>> {
|
||||
self.transport.lock().unwrap().get_stream()
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
let mut tasks = self.tasks.lock().unwrap();
|
||||
|
||||
let mut rx = self.mpsc.lock().unwrap().get_stream();
|
||||
let inflight_requests = self.inflight_requests.clone();
|
||||
tasks.spawn(async move {
|
||||
while let Some(packet) = rx.next().await {
|
||||
if let Err(err) = packet {
|
||||
tracing::error!(?err, "Failed to receive packet");
|
||||
continue;
|
||||
}
|
||||
let packet = match RpcPacket::decode(packet.unwrap().payload()) {
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "Failed to decode packet");
|
||||
continue;
|
||||
}
|
||||
Ok(packet) => packet,
|
||||
};
|
||||
|
||||
if packet.is_request {
|
||||
tracing::warn!(?packet, "Received non-response packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
let key = InflightRequestKey {
|
||||
from_peer_id: packet.to_peer,
|
||||
to_peer_id: packet.from_peer,
|
||||
transaction_id: packet.transaction_id,
|
||||
};
|
||||
|
||||
let Some(mut inflight_request) = inflight_requests.get_mut(&key) else {
|
||||
tracing::warn!(?key, "No inflight request found for key");
|
||||
continue;
|
||||
};
|
||||
|
||||
let ret = inflight_request.merger.feed(packet);
|
||||
match ret {
|
||||
Ok(Some(rpc_packet)) => {
|
||||
inflight_request.sender.send(rpc_packet).unwrap();
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "Failed to feed packet to merger");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn scoped_client<F: RpcClientFactory>(
|
||||
&self,
|
||||
from_peer_id: PeerId,
|
||||
to_peer_id: PeerId,
|
||||
domain_name: String,
|
||||
) -> F::ClientImpl {
|
||||
#[derive(Clone)]
|
||||
struct HandlerImpl<F> {
|
||||
domain_name: String,
|
||||
from_peer_id: PeerId,
|
||||
to_peer_id: PeerId,
|
||||
zc_packet_sender: MpscTunnelSender,
|
||||
inflight_requests: InflightRequestTable,
|
||||
_phan: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F: RpcClientFactory> HandlerImpl<F> {
|
||||
async fn do_rpc(
|
||||
&self,
|
||||
packets: Vec<ZCPacket>,
|
||||
rx: &mut RpcPacketReceiver,
|
||||
) -> Result<RpcPacket> {
|
||||
for packet in packets {
|
||||
self.zc_packet_sender.send(packet).await?;
|
||||
}
|
||||
|
||||
Ok(rx.recv().await.ok_or(TunnelError::Shutdown)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<F: RpcClientFactory> Handler for HandlerImpl<F> {
|
||||
type Descriptor = F::Descriptor;
|
||||
type Controller = F::Controller;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method: <Self::Descriptor as ServiceDescriptor>::Method,
|
||||
input: bytes::Bytes,
|
||||
) -> Result<bytes::Bytes> {
|
||||
let transaction_id = CUR_TID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let key = InflightRequestKey {
|
||||
from_peer_id: self.from_peer_id,
|
||||
to_peer_id: self.to_peer_id,
|
||||
transaction_id,
|
||||
};
|
||||
|
||||
defer!(self.inflight_requests.remove(&key););
|
||||
self.inflight_requests.insert(
|
||||
key.clone(),
|
||||
InflightRequest {
|
||||
sender: tx,
|
||||
merger: PacketMerger::new(),
|
||||
start_time: std::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
|
||||
let desc = self.service_descriptor();
|
||||
|
||||
let rpc_desc = RpcDescriptor {
|
||||
domain_name: self.domain_name.clone(),
|
||||
proto_name: desc.proto_name().to_string(),
|
||||
service_name: desc.name().to_string(),
|
||||
method_index: method.index() as u32,
|
||||
};
|
||||
|
||||
let rpc_req = RpcRequest {
|
||||
descriptor: Some(rpc_desc.clone()),
|
||||
request: input.into(),
|
||||
timeout_ms: ctrl.timeout_ms(),
|
||||
};
|
||||
|
||||
let packets = build_rpc_packet(
|
||||
self.from_peer_id,
|
||||
self.to_peer_id,
|
||||
rpc_desc,
|
||||
transaction_id,
|
||||
true,
|
||||
&rpc_req.encode_to_vec(),
|
||||
ctrl.trace_id(),
|
||||
);
|
||||
|
||||
let timeout_dur = std::time::Duration::from_millis(ctrl.timeout_ms() as u64);
|
||||
let rpc_packet = timeout(timeout_dur, self.do_rpc(packets, &mut rx)).await??;
|
||||
|
||||
assert_eq!(rpc_packet.transaction_id, transaction_id);
|
||||
|
||||
let rpc_resp = RpcResponse::decode(Bytes::from(rpc_packet.body))?;
|
||||
|
||||
if let Some(err) = &rpc_resp.error {
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(bytes::Bytes::from(rpc_resp.response))
|
||||
}
|
||||
}
|
||||
|
||||
F::new(HandlerImpl::<F> {
|
||||
domain_name: domain_name.to_string(),
|
||||
from_peer_id,
|
||||
to_peer_id,
|
||||
zc_packet_sender: self.mpsc.lock().unwrap().get_sink(),
|
||||
inflight_requests: self.inflight_requests.clone(),
|
||||
_phan: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inflight_count(&self) -> usize {
|
||||
self.inflight_requests.len()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::tunnel::{mpsc::MpscTunnel, Tunnel};
|
||||
|
||||
pub type RpcController = super::rpc_types::controller::BaseController;
|
||||
|
||||
pub mod client;
|
||||
pub mod packet;
|
||||
pub mod server;
|
||||
pub mod service_registry;
|
||||
pub mod standalone;
|
||||
|
||||
pub type Transport = MpscTunnel<Box<dyn Tunnel>>;
|
||||
pub type RpcTransactId = i64;
|
||||
@@ -0,0 +1,161 @@
|
||||
use prost::Message as _;
|
||||
|
||||
use crate::{
|
||||
common::PeerId,
|
||||
proto::{
|
||||
common::{RpcDescriptor, RpcPacket},
|
||||
rpc_types::error::Error,
|
||||
},
|
||||
tunnel::packet_def::{PacketType, ZCPacket},
|
||||
};
|
||||
|
||||
use super::RpcTransactId;
|
||||
|
||||
const RPC_PACKET_CONTENT_MTU: usize = 1300;
|
||||
|
||||
pub struct PacketMerger {
|
||||
first_piece: Option<RpcPacket>,
|
||||
pieces: Vec<RpcPacket>,
|
||||
last_updated: std::time::Instant,
|
||||
}
|
||||
|
||||
impl PacketMerger {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
first_piece: None,
|
||||
pieces: Vec::new(),
|
||||
last_updated: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_merge_pieces(&self) -> Option<RpcPacket> {
|
||||
if self.first_piece.is_none() || self.pieces.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for p in &self.pieces {
|
||||
// some piece is missing
|
||||
if p.total_pieces == 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// all pieces are received
|
||||
let mut body = Vec::new();
|
||||
for p in &self.pieces {
|
||||
body.extend_from_slice(&p.body);
|
||||
}
|
||||
|
||||
let mut tmpl_packet = self.first_piece.as_ref().unwrap().clone();
|
||||
tmpl_packet.total_pieces = 1;
|
||||
tmpl_packet.piece_idx = 0;
|
||||
tmpl_packet.body = body;
|
||||
|
||||
Some(tmpl_packet)
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, rpc_packet: RpcPacket) -> Result<Option<RpcPacket>, Error> {
|
||||
let total_pieces = rpc_packet.total_pieces;
|
||||
let piece_idx = rpc_packet.piece_idx;
|
||||
|
||||
if rpc_packet.descriptor.is_none() {
|
||||
return Err(Error::MalformatRpcPacket(
|
||||
"descriptor is missing".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
// for compatibility with old version
|
||||
if total_pieces == 0 && piece_idx == 0 {
|
||||
return Ok(Some(rpc_packet));
|
||||
}
|
||||
|
||||
// about 32MB max size
|
||||
if total_pieces > 32 * 1024 || total_pieces == 0 {
|
||||
return Err(Error::MalformatRpcPacket(format!(
|
||||
"total_pieces is invalid: {}",
|
||||
total_pieces
|
||||
)));
|
||||
}
|
||||
|
||||
if piece_idx >= total_pieces {
|
||||
return Err(Error::MalformatRpcPacket(
|
||||
"piece_idx >= total_pieces".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.first_piece.is_none()
|
||||
|| self.first_piece.as_ref().unwrap().transaction_id != rpc_packet.transaction_id
|
||||
|| self.first_piece.as_ref().unwrap().from_peer != rpc_packet.from_peer
|
||||
{
|
||||
self.first_piece = Some(rpc_packet.clone());
|
||||
self.pieces.clear();
|
||||
}
|
||||
|
||||
self.pieces
|
||||
.resize(total_pieces as usize, Default::default());
|
||||
self.pieces[piece_idx as usize] = rpc_packet;
|
||||
|
||||
self.last_updated = std::time::Instant::now();
|
||||
|
||||
Ok(self.try_merge_pieces())
|
||||
}
|
||||
|
||||
pub fn last_updated(&self) -> std::time::Instant {
|
||||
self.last_updated
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_rpc_packet(
|
||||
from_peer: PeerId,
|
||||
to_peer: PeerId,
|
||||
rpc_desc: RpcDescriptor,
|
||||
transaction_id: RpcTransactId,
|
||||
is_req: bool,
|
||||
content: &Vec<u8>,
|
||||
trace_id: i32,
|
||||
) -> Vec<ZCPacket> {
|
||||
let mut ret = Vec::new();
|
||||
let content_mtu = RPC_PACKET_CONTENT_MTU;
|
||||
let total_pieces = (content.len() + content_mtu - 1) / content_mtu;
|
||||
let mut cur_offset = 0;
|
||||
while cur_offset < content.len() || content.len() == 0 {
|
||||
let mut cur_len = content_mtu;
|
||||
if cur_offset + cur_len > content.len() {
|
||||
cur_len = content.len() - cur_offset;
|
||||
}
|
||||
|
||||
let mut cur_content = Vec::new();
|
||||
cur_content.extend_from_slice(&content[cur_offset..cur_offset + cur_len]);
|
||||
|
||||
let cur_packet = RpcPacket {
|
||||
from_peer,
|
||||
to_peer,
|
||||
descriptor: Some(rpc_desc.clone()),
|
||||
is_request: is_req,
|
||||
total_pieces: total_pieces as u32,
|
||||
piece_idx: (cur_offset / content_mtu) as u32,
|
||||
transaction_id,
|
||||
body: cur_content,
|
||||
trace_id,
|
||||
};
|
||||
cur_offset += cur_len;
|
||||
|
||||
let packet_type = if is_req {
|
||||
PacketType::RpcReq
|
||||
} else {
|
||||
PacketType::RpcResp
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
cur_packet.encode(&mut buf).unwrap();
|
||||
let mut zc_packet = ZCPacket::new_with_payload(&buf);
|
||||
zc_packet.fill_peer_manager_hdr(from_peer, to_peer, packet_type as u8);
|
||||
ret.push(zc_packet);
|
||||
|
||||
if content.len() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use prost::Message;
|
||||
use tokio::{task::JoinSet, time::timeout};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use crate::{
|
||||
common::{join_joinset_background, PeerId},
|
||||
proto::{
|
||||
common::{self, RpcDescriptor, RpcPacket, RpcRequest, RpcResponse},
|
||||
rpc_types::error::Result,
|
||||
},
|
||||
tunnel::{
|
||||
mpsc::{MpscTunnel, MpscTunnelSender},
|
||||
ring::create_ring_tunnel_pair,
|
||||
Tunnel, ZCPacketStream,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
packet::{build_rpc_packet, PacketMerger},
|
||||
service_registry::ServiceRegistry,
|
||||
RpcController, Transport,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct PacketMergerKey {
|
||||
from_peer_id: PeerId,
|
||||
rpc_desc: RpcDescriptor,
|
||||
transaction_id: i64,
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
registry: Arc<ServiceRegistry>,
|
||||
|
||||
mpsc: Mutex<Option<MpscTunnel<Box<dyn Tunnel>>>>,
|
||||
|
||||
transport: Mutex<Transport>,
|
||||
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
packet_mergers: Arc<DashMap<PacketMergerKey, PacketMerger>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new() -> Self {
|
||||
Server::new_with_registry(Arc::new(ServiceRegistry::new()))
|
||||
}
|
||||
|
||||
pub fn new_with_registry(registry: Arc<ServiceRegistry>) -> Self {
|
||||
let (ring_a, ring_b) = create_ring_tunnel_pair();
|
||||
|
||||
Self {
|
||||
registry,
|
||||
mpsc: Mutex::new(Some(MpscTunnel::new(ring_a))),
|
||||
transport: Mutex::new(MpscTunnel::new(ring_b)),
|
||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||
packet_mergers: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registry(&self) -> &ServiceRegistry {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub fn get_transport_sink(&self) -> MpscTunnelSender {
|
||||
self.transport.lock().unwrap().get_sink()
|
||||
}
|
||||
|
||||
pub fn get_transport_stream(&self) -> Pin<Box<dyn ZCPacketStream>> {
|
||||
self.transport.lock().unwrap().get_stream()
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
let tasks = self.tasks.clone();
|
||||
join_joinset_background(tasks.clone(), "rpc server".to_string());
|
||||
|
||||
let mpsc = self.mpsc.lock().unwrap().take().unwrap();
|
||||
|
||||
let packet_merges = self.packet_mergers.clone();
|
||||
let reg = self.registry.clone();
|
||||
let t = tasks.clone();
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
let mut mpsc = mpsc;
|
||||
let mut rx = mpsc.get_stream();
|
||||
|
||||
while let Some(packet) = rx.next().await {
|
||||
if let Err(err) = packet {
|
||||
tracing::error!(?err, "Failed to receive packet");
|
||||
continue;
|
||||
}
|
||||
let packet = match common::RpcPacket::decode(packet.unwrap().payload()) {
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "Failed to decode packet");
|
||||
continue;
|
||||
}
|
||||
Ok(packet) => packet,
|
||||
};
|
||||
|
||||
if !packet.is_request {
|
||||
tracing::warn!(?packet, "Received non-request packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
let key = PacketMergerKey {
|
||||
from_peer_id: packet.from_peer,
|
||||
rpc_desc: packet.descriptor.clone().unwrap_or_default(),
|
||||
transaction_id: packet.transaction_id,
|
||||
};
|
||||
|
||||
let ret = packet_merges
|
||||
.entry(key.clone())
|
||||
.or_insert_with(PacketMerger::new)
|
||||
.feed(packet);
|
||||
|
||||
match ret {
|
||||
Ok(Some(packet)) => {
|
||||
packet_merges.remove(&key);
|
||||
t.lock().unwrap().spawn(Self::handle_rpc(
|
||||
mpsc.get_sink(),
|
||||
packet,
|
||||
reg.clone(),
|
||||
));
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to feed packet to merger, {}", err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let packet_mergers = self.packet_mergers.clone();
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
packet_mergers.retain(|_, v| v.last_updated().elapsed().as_secs() < 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_rpc_request(packet: RpcPacket, reg: Arc<ServiceRegistry>) -> Result<Bytes> {
|
||||
let rpc_request = RpcRequest::decode(Bytes::from(packet.body))?;
|
||||
let timeout_duration = std::time::Duration::from_millis(rpc_request.timeout_ms as u64);
|
||||
let ctrl = RpcController {};
|
||||
Ok(timeout(
|
||||
timeout_duration,
|
||||
reg.call_method(
|
||||
packet.descriptor.unwrap(),
|
||||
ctrl,
|
||||
Bytes::from(rpc_request.request),
|
||||
),
|
||||
)
|
||||
.await??)
|
||||
}
|
||||
|
||||
async fn handle_rpc(sender: MpscTunnelSender, packet: RpcPacket, reg: Arc<ServiceRegistry>) {
|
||||
let from_peer = packet.from_peer;
|
||||
let to_peer = packet.to_peer;
|
||||
let transaction_id = packet.transaction_id;
|
||||
let trace_id = packet.trace_id;
|
||||
let desc = packet.descriptor.clone().unwrap();
|
||||
|
||||
let mut resp_msg = RpcResponse::default();
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let resp_bytes = Self::handle_rpc_request(packet, reg).await;
|
||||
|
||||
match &resp_bytes {
|
||||
Ok(r) => {
|
||||
resp_msg.response = r.clone().into();
|
||||
}
|
||||
Err(err) => {
|
||||
resp_msg.error = Some(err.into());
|
||||
}
|
||||
};
|
||||
resp_msg.runtime_us = now.elapsed().as_micros() as u64;
|
||||
|
||||
let packets = build_rpc_packet(
|
||||
to_peer,
|
||||
from_peer,
|
||||
desc,
|
||||
transaction_id,
|
||||
false,
|
||||
&resp_msg.encode_to_vec(),
|
||||
trace_id,
|
||||
);
|
||||
|
||||
for packet in packets {
|
||||
if let Err(err) = sender.send(packet).await {
|
||||
tracing::error!(?err, "Failed to send response packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inflight_count(&self) -> usize {
|
||||
self.packet_mergers.len()
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.transport.lock().unwrap().close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::proto::common::RpcDescriptor;
|
||||
use crate::proto::rpc_types;
|
||||
use crate::proto::rpc_types::descriptor::ServiceDescriptor;
|
||||
use crate::proto::rpc_types::handler::{Handler, HandlerExt};
|
||||
|
||||
use super::RpcController;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ServiceKey {
|
||||
pub domain_name: String,
|
||||
pub service_name: String,
|
||||
pub proto_name: String,
|
||||
}
|
||||
|
||||
impl From<&RpcDescriptor> for ServiceKey {
|
||||
fn from(desc: &RpcDescriptor) -> Self {
|
||||
Self {
|
||||
domain_name: desc.domain_name.to_string(),
|
||||
service_name: desc.service_name.to_string(),
|
||||
proto_name: desc.proto_name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ServiceEntry {
|
||||
service: Arc<Box<dyn HandlerExt<Controller = RpcController>>>,
|
||||
}
|
||||
|
||||
impl ServiceEntry {
|
||||
fn new<H: Handler<Controller = RpcController>>(h: H) -> Self {
|
||||
Self {
|
||||
service: Arc::new(Box::new(h)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn call_method(
|
||||
&self,
|
||||
ctrl: RpcController,
|
||||
method_index: u8,
|
||||
input: bytes::Bytes,
|
||||
) -> rpc_types::error::Result<bytes::Bytes> {
|
||||
self.service.call_method(ctrl, method_index, input).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceRegistry {
|
||||
table: DashMap<ServiceKey, ServiceEntry>,
|
||||
}
|
||||
|
||||
impl ServiceRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
table: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<H: Handler<Controller = RpcController>>(&self, h: H, domain_name: &str) {
|
||||
let desc = h.service_descriptor();
|
||||
let key = ServiceKey {
|
||||
domain_name: domain_name.to_string(),
|
||||
service_name: desc.name().to_string(),
|
||||
proto_name: desc.proto_name().to_string(),
|
||||
};
|
||||
let entry = ServiceEntry::new(h);
|
||||
self.table.insert(key, entry);
|
||||
}
|
||||
|
||||
pub fn unregister<H: Handler<Controller = RpcController>>(
|
||||
&self,
|
||||
h: H,
|
||||
domain_name: &str,
|
||||
) -> Option<()> {
|
||||
let desc = h.service_descriptor();
|
||||
let key = ServiceKey {
|
||||
domain_name: domain_name.to_string(),
|
||||
service_name: desc.name().to_string(),
|
||||
proto_name: desc.proto_name().to_string(),
|
||||
};
|
||||
self.table.remove(&key).map(|_| ())
|
||||
}
|
||||
|
||||
pub async fn call_method(
|
||||
&self,
|
||||
rpc_desc: RpcDescriptor,
|
||||
ctrl: RpcController,
|
||||
input: bytes::Bytes,
|
||||
) -> rpc_types::error::Result<bytes::Bytes> {
|
||||
let service_key = ServiceKey::from(&rpc_desc);
|
||||
let method_index = rpc_desc.method_index as u8;
|
||||
let entry = self
|
||||
.table
|
||||
.get(&service_key)
|
||||
.ok_or(rpc_types::error::Error::InvalidServiceKey(
|
||||
service_key.service_name.clone(),
|
||||
service_key.proto_name.clone(),
|
||||
))?
|
||||
.clone();
|
||||
entry.call_method(ctrl, method_index, input).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
use std::{
|
||||
sync::{atomic::AtomicU32, Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use futures::{SinkExt as _, StreamExt};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::{
|
||||
common::join_joinset_background,
|
||||
proto::rpc_types::{__rt::RpcClientFactory, error::Error},
|
||||
tunnel::{Tunnel, TunnelConnector, TunnelListener},
|
||||
};
|
||||
|
||||
use super::{client::Client, server::Server, service_registry::ServiceRegistry};
|
||||
|
||||
struct StandAloneServerOneTunnel {
|
||||
tunnel: Box<dyn Tunnel>,
|
||||
rpc_server: Server,
|
||||
}
|
||||
|
||||
impl StandAloneServerOneTunnel {
|
||||
pub fn new(tunnel: Box<dyn Tunnel>, registry: Arc<ServiceRegistry>) -> Self {
|
||||
let rpc_server = Server::new_with_registry(registry);
|
||||
StandAloneServerOneTunnel { tunnel, rpc_server }
|
||||
}
|
||||
|
||||
pub async fn run(self) {
|
||||
use tokio_stream::StreamExt as _;
|
||||
|
||||
let (tunnel_rx, tunnel_tx) = self.tunnel.split();
|
||||
let (rpc_rx, rpc_tx) = (
|
||||
self.rpc_server.get_transport_stream(),
|
||||
self.rpc_server.get_transport_sink(),
|
||||
);
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
tasks.spawn(async move {
|
||||
let ret = tunnel_rx.timeout(Duration::from_secs(60));
|
||||
tokio::pin!(ret);
|
||||
while let Ok(Some(Ok(p))) = ret.try_next().await {
|
||||
if let Err(e) = rpc_tx.send(p).await {
|
||||
tracing::error!("tunnel_rx send to rpc_tx error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tracing::info!("forward tunnel_rx to rpc_tx done");
|
||||
});
|
||||
|
||||
tasks.spawn(async move {
|
||||
let ret = rpc_rx.forward(tunnel_tx).await;
|
||||
tracing::info!("rpc_rx forward tunnel_tx done: {:?}", ret);
|
||||
});
|
||||
|
||||
self.rpc_server.run();
|
||||
|
||||
while let Some(ret) = tasks.join_next().await {
|
||||
self.rpc_server.close();
|
||||
tracing::info!("task done: {:?}", ret);
|
||||
}
|
||||
|
||||
tracing::info!("all tasks done");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StandAloneServer<L> {
|
||||
registry: Arc<ServiceRegistry>,
|
||||
listener: Option<L>,
|
||||
inflight_server: Arc<AtomicU32>,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
}
|
||||
|
||||
impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
||||
pub fn new(listener: L) -> Self {
|
||||
StandAloneServer {
|
||||
registry: Arc::new(ServiceRegistry::new()),
|
||||
listener: Some(listener),
|
||||
inflight_server: Arc::new(AtomicU32::new(0)),
|
||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registry(&self) -> &ServiceRegistry {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
pub async fn serve(&mut self) -> Result<(), Error> {
|
||||
let tasks = self.tasks.clone();
|
||||
let mut listener = self.listener.take().unwrap();
|
||||
let registry = self.registry.clone();
|
||||
|
||||
join_joinset_background(tasks.clone(), "standalone server tasks".to_string());
|
||||
|
||||
listener
|
||||
.listen()
|
||||
.await
|
||||
.with_context(|| "failed to listen")?;
|
||||
|
||||
let inflight_server = self.inflight_server.clone();
|
||||
|
||||
self.tasks.lock().unwrap().spawn(async move {
|
||||
while let Ok(tunnel) = listener.accept().await {
|
||||
let server = StandAloneServerOneTunnel::new(tunnel, registry.clone());
|
||||
let inflight_server = inflight_server.clone();
|
||||
inflight_server.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
server.run().await;
|
||||
inflight_server.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
panic!("standalone server listener exit");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn inflight_server(&self) -> u32 {
|
||||
self.inflight_server
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
struct StandAloneClientOneTunnel {
|
||||
rpc_client: Client,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
error: Arc<Mutex<Option<Error>>>,
|
||||
}
|
||||
|
||||
impl StandAloneClientOneTunnel {
|
||||
pub fn new(tunnel: Box<dyn Tunnel>) -> Self {
|
||||
let rpc_client = Client::new();
|
||||
let (mut rpc_rx, rpc_tx) = (
|
||||
rpc_client.get_transport_stream(),
|
||||
rpc_client.get_transport_sink(),
|
||||
);
|
||||
let tasks = Arc::new(Mutex::new(JoinSet::new()));
|
||||
|
||||
let (mut tunnel_rx, mut tunnel_tx) = tunnel.split();
|
||||
|
||||
let error_store = Arc::new(Mutex::new(None));
|
||||
|
||||
let error = error_store.clone();
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
while let Some(p) = rpc_rx.next().await {
|
||||
match p {
|
||||
Ok(p) => {
|
||||
if let Err(e) = tunnel_tx
|
||||
.send(p)
|
||||
.await
|
||||
.with_context(|| "failed to send packet")
|
||||
{
|
||||
*error.lock().unwrap() = Some(e.into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
*error.lock().unwrap() = Some(anyhow::Error::from(e).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*error.lock().unwrap() = Some(anyhow::anyhow!("rpc_rx next exit").into());
|
||||
});
|
||||
|
||||
let error = error_store.clone();
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
while let Some(p) = tunnel_rx.next().await {
|
||||
match p {
|
||||
Ok(p) => {
|
||||
if let Err(e) = rpc_tx
|
||||
.send(p)
|
||||
.await
|
||||
.with_context(|| "failed to send packet")
|
||||
{
|
||||
*error.lock().unwrap() = Some(e.into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
*error.lock().unwrap() = Some(anyhow::Error::from(e).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*error.lock().unwrap() = Some(anyhow::anyhow!("tunnel_rx next exit").into());
|
||||
});
|
||||
|
||||
rpc_client.run();
|
||||
|
||||
StandAloneClientOneTunnel {
|
||||
rpc_client,
|
||||
tasks,
|
||||
error: error_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_error(&self) -> Option<Error> {
|
||||
self.error.lock().unwrap().take()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StandAloneClient<C: TunnelConnector> {
|
||||
connector: C,
|
||||
client: Option<StandAloneClientOneTunnel>,
|
||||
}
|
||||
|
||||
impl<C: TunnelConnector> StandAloneClient<C> {
|
||||
pub fn new(connector: C) -> Self {
|
||||
StandAloneClient {
|
||||
connector,
|
||||
client: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, Error> {
|
||||
Ok(self.connector.connect().await.with_context(|| {
|
||||
format!(
|
||||
"failed to connect to server: {:?}",
|
||||
self.connector.remote_url()
|
||||
)
|
||||
})?)
|
||||
}
|
||||
|
||||
pub async fn scoped_client<F: RpcClientFactory>(
|
||||
&mut self,
|
||||
domain_name: String,
|
||||
) -> Result<F::ClientImpl, Error> {
|
||||
let mut c = self.client.take();
|
||||
let error = c.as_ref().and_then(|c| c.take_error());
|
||||
if c.is_none() || error.is_some() {
|
||||
tracing::info!("reconnect due to error: {:?}", error);
|
||||
let tunnel = self.connect().await?;
|
||||
c = Some(StandAloneClientOneTunnel::new(tunnel));
|
||||
}
|
||||
|
||||
self.client = c;
|
||||
|
||||
Ok(self
|
||||
.client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rpc_client
|
||||
.scoped_client::<F>(1, 1, domain_name))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Utility functions used by generated code; this is *not* part of the crate's public API!
|
||||
use bytes;
|
||||
use prost;
|
||||
|
||||
use super::controller;
|
||||
use super::descriptor;
|
||||
use super::descriptor::ServiceDescriptor;
|
||||
use super::error;
|
||||
use super::handler;
|
||||
use super::handler::Handler;
|
||||
|
||||
/// Efficiently decode a particular message type from a byte buffer.
|
||||
pub fn decode<M>(buf: bytes::Bytes) -> error::Result<M>
|
||||
where
|
||||
M: prost::Message + Default,
|
||||
{
|
||||
let message = prost::Message::decode(buf)?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Efficiently encode a particular message into a byte buffer.
|
||||
pub fn encode<M>(message: M) -> error::Result<bytes::Bytes>
|
||||
where
|
||||
M: prost::Message,
|
||||
{
|
||||
let len = prost::Message::encoded_len(&message);
|
||||
let mut buf = ::bytes::BytesMut::with_capacity(len);
|
||||
prost::Message::encode(&message, &mut buf)?;
|
||||
Ok(buf.freeze())
|
||||
}
|
||||
|
||||
pub async fn call_method<H, I, O>(
|
||||
handler: H,
|
||||
ctrl: H::Controller,
|
||||
method: <H::Descriptor as descriptor::ServiceDescriptor>::Method,
|
||||
input: I,
|
||||
) -> super::error::Result<O>
|
||||
where
|
||||
H: handler::Handler,
|
||||
I: prost::Message,
|
||||
O: prost::Message + Default,
|
||||
{
|
||||
type Error = super::error::Error;
|
||||
let input_bytes = encode(input)?;
|
||||
let ret_msg = handler.call(ctrl, method, input_bytes).await?;
|
||||
decode(ret_msg)
|
||||
}
|
||||
|
||||
pub trait RpcClientFactory: Clone + Send + Sync + 'static {
|
||||
type Descriptor: ServiceDescriptor + Default;
|
||||
type ClientImpl;
|
||||
type Controller: controller::Controller;
|
||||
|
||||
fn new(
|
||||
handler: impl Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>,
|
||||
) -> Self::ClientImpl;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
pub trait Controller: Send + Sync + 'static {
|
||||
fn timeout_ms(&self) -> i32 {
|
||||
5000
|
||||
}
|
||||
|
||||
fn set_timeout_ms(&mut self, _timeout_ms: i32) {}
|
||||
|
||||
fn set_trace_id(&mut self, _trace_id: i32) {}
|
||||
|
||||
fn trace_id(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BaseController {}
|
||||
|
||||
impl Controller for BaseController {}
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Traits for defining generic service descriptor definitions.
|
||||
//!
|
||||
//! These traits are built on the assumption that some form of code generation is being used (e.g.
|
||||
//! using only `&'static str`s) but it's of course possible to implement these traits manually.
|
||||
use std::any;
|
||||
use std::fmt;
|
||||
|
||||
/// A descriptor for an available RPC service.
|
||||
pub trait ServiceDescriptor: Clone + fmt::Debug + Send + Sync {
|
||||
/// The associated type of method descriptors.
|
||||
type Method: MethodDescriptor + fmt::Debug + TryFrom<u8>;
|
||||
|
||||
/// The name of the service, used in Rust code and perhaps for human readability.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// The raw protobuf name of the service.
|
||||
fn proto_name(&self) -> &'static str;
|
||||
|
||||
/// The package name of the service.
|
||||
fn package(&self) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
/// All of the available methods on the service.
|
||||
fn methods(&self) -> &'static [Self::Method];
|
||||
}
|
||||
|
||||
/// A descriptor for a method available on an RPC service.
|
||||
pub trait MethodDescriptor: Clone + Copy + fmt::Debug + Send + Sync {
|
||||
/// The name of the service, used in Rust code and perhaps for human readability.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// The raw protobuf name of the service.
|
||||
fn proto_name(&self) -> &'static str;
|
||||
|
||||
/// The Rust `TypeId` for the input that this method accepts.
|
||||
fn input_type(&self) -> any::TypeId;
|
||||
|
||||
/// The raw protobuf name for the input type that this method accepts.
|
||||
fn input_proto_type(&self) -> &'static str;
|
||||
|
||||
/// The Rust `TypeId` for the output that this method produces.
|
||||
fn output_type(&self) -> any::TypeId;
|
||||
|
||||
/// The raw protobuf name for the output type that this method produces.
|
||||
fn output_proto_type(&self) -> &'static str;
|
||||
|
||||
/// The index of the method in the service descriptor.
|
||||
fn index(&self) -> u8;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Error type definitions for errors that can occur during RPC interactions.
|
||||
use std::result;
|
||||
|
||||
use prost;
|
||||
use thiserror;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("rust tun error {0}")]
|
||||
ExecutionError(#[from] anyhow::Error),
|
||||
|
||||
#[error("Decode error: {0}")]
|
||||
DecodeError(#[from] prost::DecodeError),
|
||||
|
||||
#[error("Encode error: {0}")]
|
||||
EncodeError(#[from] prost::EncodeError),
|
||||
|
||||
#[error("Invalid method index: {0}, service: {1}")]
|
||||
InvalidMethodIndex(u8, String),
|
||||
|
||||
#[error("Invalid service name: {0}, proto name: {1}")]
|
||||
InvalidServiceKey(String, String),
|
||||
|
||||
#[error("Invalid packet: {0}")]
|
||||
MalformatRpcPacket(String),
|
||||
|
||||
#[error("Timeout: {0}")]
|
||||
Timeout(#[from] tokio::time::error::Elapsed),
|
||||
|
||||
#[error("Tunnel error: {0}")]
|
||||
TunnelError(#[from] crate::tunnel::TunnelError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -0,0 +1,67 @@
|
||||
//! Traits for defining generic RPC handlers.
|
||||
use super::{
|
||||
controller::Controller,
|
||||
descriptor::{self, ServiceDescriptor},
|
||||
};
|
||||
use bytes;
|
||||
|
||||
/// An implementation of a specific RPC handler.
|
||||
///
|
||||
/// This can be an actual implementation of a service, or something that will send a request over
|
||||
/// a network to fulfill a request.
|
||||
#[async_trait::async_trait]
|
||||
pub trait Handler: Clone + Send + Sync + 'static {
|
||||
/// The service descriptor for the service whose requests this handler can handle.
|
||||
type Descriptor: descriptor::ServiceDescriptor + Default;
|
||||
|
||||
type Controller: super::controller::Controller;
|
||||
///
|
||||
|
||||
/// Perform a raw call to the specified service and method.
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method: <Self::Descriptor as descriptor::ServiceDescriptor>::Method,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes>;
|
||||
|
||||
fn service_descriptor(&self) -> Self::Descriptor {
|
||||
Self::Descriptor::default()
|
||||
}
|
||||
|
||||
fn get_method_from_index(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> super::error::Result<<Self::Descriptor as descriptor::ServiceDescriptor>::Method> {
|
||||
let desc = self.service_descriptor();
|
||||
<Self::Descriptor as descriptor::ServiceDescriptor>::Method::try_from(index)
|
||||
.map_err(|_| super::error::Error::InvalidMethodIndex(index, desc.name().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait HandlerExt: Send + Sync + 'static {
|
||||
type Controller;
|
||||
|
||||
async fn call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method_index: u8,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<C: Controller, T: Handler<Controller = C>> HandlerExt for T {
|
||||
type Controller = C;
|
||||
|
||||
async fn call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method_index: u8,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes> {
|
||||
let method = self.get_method_from_index(method_index)?;
|
||||
self.call(ctrl, method, input).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod __rt;
|
||||
pub mod controller;
|
||||
pub mod descriptor;
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
@@ -0,0 +1,24 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package tests;
|
||||
|
||||
/// The Greeting service. This service is used to generate greetings for various
|
||||
/// use-cases.
|
||||
service Greeting {
|
||||
// Generates a "hello" greeting based on the supplied info.
|
||||
rpc SayHello(SayHelloRequest) returns (SayHelloResponse);
|
||||
// Generates a "goodbye" greeting based on the supplied info.
|
||||
rpc SayGoodbye(SayGoodbyeRequest) returns (SayGoodbyeResponse);
|
||||
}
|
||||
|
||||
// The request for an `Greeting.SayHello` call.
|
||||
message SayHelloRequest { string name = 1; }
|
||||
|
||||
// The response for an `Greeting.SayHello` call.
|
||||
message SayHelloResponse { string greeting = 1; }
|
||||
|
||||
// The request for an `Greeting.SayGoodbye` call.
|
||||
message SayGoodbyeRequest { string name = 1; }
|
||||
|
||||
// The response for an `Greeting.SayGoodbye` call.
|
||||
message SayGoodbyeResponse { string greeting = 1; }
|
||||
@@ -0,0 +1,225 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use futures::StreamExt as _;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use super::rpc_impl::RpcController;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GreetingService {
|
||||
pub delay_ms: u64,
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Greeting for GreetingService {
|
||||
type Controller = RpcController;
|
||||
async fn say_hello(
|
||||
&self,
|
||||
_ctrl: Self::Controller,
|
||||
input: SayHelloRequest,
|
||||
) -> crate::proto::rpc_types::error::Result<SayHelloResponse> {
|
||||
let resp = SayHelloResponse {
|
||||
greeting: format!("{} {}!", self.prefix, input.name),
|
||||
};
|
||||
tokio::time::sleep(std::time::Duration::from_millis(self.delay_ms)).await;
|
||||
Ok(resp)
|
||||
}
|
||||
/// Generates a "goodbye" greeting based on the supplied info.
|
||||
async fn say_goodbye(
|
||||
&self,
|
||||
_ctrl: Self::Controller,
|
||||
input: SayGoodbyeRequest,
|
||||
) -> crate::proto::rpc_types::error::Result<SayGoodbyeResponse> {
|
||||
let resp = SayGoodbyeResponse {
|
||||
greeting: format!("Goodbye, {}!", input.name),
|
||||
};
|
||||
tokio::time::sleep(std::time::Duration::from_millis(self.delay_ms)).await;
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::proto::rpc_impl::client::Client;
|
||||
use crate::proto::rpc_impl::server::Server;
|
||||
|
||||
struct TestContext {
|
||||
client: Client,
|
||||
server: Server,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
fn new() -> Self {
|
||||
let rpc_server = Server::new();
|
||||
rpc_server.run();
|
||||
|
||||
let client = Client::new();
|
||||
client.run();
|
||||
|
||||
let tasks = Arc::new(Mutex::new(JoinSet::new()));
|
||||
let (mut rx, tx) = (
|
||||
rpc_server.get_transport_stream(),
|
||||
client.get_transport_sink(),
|
||||
);
|
||||
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
while let Some(Ok(packet)) = rx.next().await {
|
||||
if let Err(err) = tx.send(packet).await {
|
||||
println!("{:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (mut rx, tx) = (
|
||||
client.get_transport_stream(),
|
||||
rpc_server.get_transport_sink(),
|
||||
);
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
while let Some(Ok(packet)) = rx.next().await {
|
||||
if let Err(err) = tx.send(packet).await {
|
||||
println!("{:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
client,
|
||||
server: rpc_server,
|
||||
tasks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn random_string(len: usize) -> String {
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
let s: Vec<u8> = std::iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
.take(len)
|
||||
.collect();
|
||||
String::from_utf8(s).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_basic_test() {
|
||||
let ctx = TestContext::new();
|
||||
|
||||
let server = GreetingServer::new(GreetingService {
|
||||
delay_ms: 0,
|
||||
prefix: "Hello".to_string(),
|
||||
});
|
||||
ctx.server.registry().register(server, "");
|
||||
|
||||
let out = ctx
|
||||
.client
|
||||
.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "".to_string());
|
||||
|
||||
// small size req and resp
|
||||
|
||||
let ctrl = RpcController {};
|
||||
let input = SayHelloRequest {
|
||||
name: "world".to_string(),
|
||||
};
|
||||
let ret = out.say_hello(ctrl, input).await;
|
||||
assert_eq!(ret.unwrap().greeting, "Hello world!");
|
||||
|
||||
let ctrl = RpcController {};
|
||||
let input = SayGoodbyeRequest {
|
||||
name: "world".to_string(),
|
||||
};
|
||||
let ret = out.say_goodbye(ctrl, input).await;
|
||||
assert_eq!(ret.unwrap().greeting, "Goodbye, world!");
|
||||
|
||||
// large size req and resp
|
||||
let ctrl = RpcController {};
|
||||
let name = random_string(20 * 1024 * 1024);
|
||||
let input = SayGoodbyeRequest { name: name.clone() };
|
||||
let ret = out.say_goodbye(ctrl, input).await;
|
||||
assert_eq!(ret.unwrap().greeting, format!("Goodbye, {}!", name));
|
||||
|
||||
assert_eq!(0, ctx.client.inflight_count());
|
||||
assert_eq!(0, ctx.server.inflight_count());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rpc_timeout_test() {
|
||||
let ctx = TestContext::new();
|
||||
|
||||
let server = GreetingServer::new(GreetingService {
|
||||
delay_ms: 10000,
|
||||
prefix: "Hello".to_string(),
|
||||
});
|
||||
ctx.server.registry().register(server, "test");
|
||||
|
||||
let out = ctx
|
||||
.client
|
||||
.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "test".to_string());
|
||||
|
||||
let ctrl = RpcController {};
|
||||
let input = SayHelloRequest {
|
||||
name: "world".to_string(),
|
||||
};
|
||||
let ret = out.say_hello(ctrl, input).await;
|
||||
assert!(ret.is_err());
|
||||
assert!(matches!(
|
||||
ret.unwrap_err(),
|
||||
crate::proto::rpc_types::error::Error::Timeout(_)
|
||||
));
|
||||
|
||||
assert_eq!(0, ctx.client.inflight_count());
|
||||
assert_eq!(0, ctx.server.inflight_count());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn standalone_rpc_test() {
|
||||
use crate::proto::rpc_impl::standalone::{StandAloneClient, StandAloneServer};
|
||||
use crate::tunnel::tcp::{TcpTunnelConnector, TcpTunnelListener};
|
||||
|
||||
let mut server = StandAloneServer::new(TcpTunnelListener::new(
|
||||
"tcp://0.0.0.0:33455".parse().unwrap(),
|
||||
));
|
||||
let service = GreetingServer::new(GreetingService {
|
||||
delay_ms: 0,
|
||||
prefix: "Hello".to_string(),
|
||||
});
|
||||
server.registry().register(service, "test");
|
||||
server.serve().await.unwrap();
|
||||
|
||||
let mut client = StandAloneClient::new(TcpTunnelConnector::new(
|
||||
"tcp://127.0.0.1:33455".parse().unwrap(),
|
||||
));
|
||||
|
||||
let out = client
|
||||
.scoped_client::<GreetingClientFactory<RpcController>>("test".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let ctrl = RpcController {};
|
||||
let input = SayHelloRequest {
|
||||
name: "world".to_string(),
|
||||
};
|
||||
let ret = out.say_hello(ctrl, input).await;
|
||||
assert_eq!(ret.unwrap().greeting, "Hello world!");
|
||||
|
||||
let out = client
|
||||
.scoped_client::<GreetingClientFactory<RpcController>>("test".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let ctrl = RpcController {};
|
||||
let input = SayGoodbyeRequest {
|
||||
name: "world".to_string(),
|
||||
};
|
||||
let ret = out.say_goodbye(ctrl, input).await;
|
||||
assert_eq!(ret.unwrap().greeting, "Goodbye, world!");
|
||||
|
||||
drop(client);
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
assert_eq!(0, server.inflight_server());
|
||||
}
|
||||
Reference in New Issue
Block a user