starpc 0.52.0 → 0.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -8
- package/dist/echo/client-test.d.ts +1 -1
- package/dist/echo/client-test.js +110 -2
- package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
- package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
- package/dist/integration/cross-language/ts-client.js +50 -37
- package/dist/integration/cross-language/ts-server.js +1 -36
- package/dist/rpcstream/rpcstream.d.ts +5 -1
- package/dist/rpcstream/rpcstream.js +75 -28
- package/dist/rpcstream/rpcstream.test.d.ts +1 -0
- package/dist/rpcstream/rpcstream.test.js +92 -0
- package/dist/srpc/client-rpc.d.ts +1 -0
- package/dist/srpc/client-rpc.js +9 -0
- package/dist/srpc/client-rpc.test.d.ts +1 -0
- package/dist/srpc/client-rpc.test.js +50 -0
- package/dist/srpc/client.js +18 -4
- package/dist/srpc/common-rpc.d.ts +5 -0
- package/dist/srpc/common-rpc.js +37 -8
- package/dist/srpc/common-rpc.test.js +2 -0
- package/dist/srpc/errors.d.ts +2 -0
- package/dist/srpc/errors.js +14 -0
- package/dist/srpc/index.d.ts +1 -1
- package/dist/srpc/index.js +1 -1
- package/dist/srpc/packet-codec.test.d.ts +1 -0
- package/dist/srpc/packet-codec.test.js +75 -0
- package/dist/srpc/packet.d.ts +1 -1
- package/dist/srpc/packet.js +11 -1
- package/dist/srpc/server.js +19 -6
- package/dist/srpc/server.test.js +43 -0
- package/dist/srpc/stream.d.ts +4 -1
- package/dist/srpc/stream.js +62 -3
- package/dist/srpc/stream.test.js +110 -1
- package/dist/srpc/termination.d.ts +27 -0
- package/dist/srpc/termination.js +56 -0
- package/dist/srpc/termination.test.d.ts +1 -0
- package/dist/srpc/termination.test.js +24 -0
- package/dist/testdata/packet-codec-vectors.json +64 -0
- package/echo/Cargo.toml +8 -0
- package/echo/client-test.ts +124 -2
- package/echo/echo_e2e_test.cpp +542 -985
- package/echo/echo_pb2.py +40 -0
- package/echo/echo_pb2.pyi +13 -0
- package/echo/echo_srpc.pb.cpp +3 -5
- package/echo/echo_srpc.pb.go +6 -9
- package/echo/echo_srpc.pb.hpp +23 -3
- package/echo/echo_srpc.pb.rs +61 -26
- package/echo/echo_srpc.py +309 -0
- package/echo/echo_srpc.pyi +85 -0
- package/echo/half_close_test.rs +178 -0
- package/echo/integration_server.rs +14 -21
- package/echo/main.rs +28 -48
- package/go.mod +3 -3
- package/go.sum +6 -8
- package/integration/cross-language/cpp-client.cpp +446 -402
- package/integration/cross-language/cpp-pipe-server.cpp +139 -0
- package/integration/cross-language/cpp-pipe_test.go +114 -0
- package/integration/cross-language/cpp-server.cpp +222 -186
- package/integration/cross-language/go-client/main.go +79 -3
- package/integration/cross-language/python-client.py +146 -0
- package/integration/cross-language/python-server.py +140 -0
- package/integration/cross-language/run.bash +190 -65
- package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
- package/integration/cross-language/tcp-packet-stream.ts +121 -0
- package/integration/cross-language/ts-client.ts +62 -40
- package/integration/cross-language/ts-server.ts +1 -45
- package/mock/mock_pb2.py +38 -0
- package/mock/mock_pb2.pyi +11 -0
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +10 -9
- package/mock/mock_srpc.py +71 -0
- package/mock/mock_srpc.pyi +27 -0
- package/package.json +19 -5
- package/srpc/CMakeLists.txt +3 -58
- package/srpc/README-cpp.md +69 -0
- package/srpc/__init__.py +0 -0
- package/srpc/accept.go +10 -4
- package/srpc/accept_test.go +25 -0
- package/srpc/build.rs +129 -34
- package/srpc/client-half-close_test.go +152 -0
- package/srpc/client-invoker.go +32 -42
- package/srpc/client-invoker_test.go +53 -0
- package/srpc/client-rpc.cpp +65 -80
- package/srpc/client-rpc.go +32 -33
- package/srpc/client-rpc.hpp +52 -38
- package/srpc/client-rpc.test.ts +67 -0
- package/srpc/client-rpc.ts +10 -0
- package/srpc/client-rpc_test.go +76 -0
- package/srpc/client.cpp +99 -87
- package/srpc/client.hpp +46 -35
- package/srpc/client.rs +78 -38
- package/srpc/client.ts +20 -4
- package/srpc/client_half_close_test.cc +331 -0
- package/srpc/client_invoker.rs +75 -0
- package/srpc/codec.rs +6 -0
- package/srpc/common-rpc.cpp +186 -146
- package/srpc/common-rpc.go +5 -4
- package/srpc/common-rpc.hpp +119 -89
- package/srpc/common-rpc.test.ts +2 -0
- package/srpc/common-rpc.ts +43 -8
- package/srpc/common-rpc_test.go +23 -0
- package/srpc/error.rs +19 -2
- package/srpc/errors.hpp +80 -50
- package/srpc/errors.ts +16 -0
- package/srpc/handler.hpp +9 -7
- package/srpc/handler.rs +7 -45
- package/srpc/index.ts +2 -0
- package/srpc/invoker.hpp +82 -55
- package/srpc/invoker.rs +14 -52
- package/srpc/lib.rs +2 -0
- package/srpc/message.hpp +17 -12
- package/srpc/msg-stream.go +1 -0
- package/srpc/msg-stream.hpp +108 -61
- package/srpc/mux.cpp +84 -84
- package/srpc/mux.hpp +48 -38
- package/srpc/mux.rs +4 -4
- package/srpc/net.go +12 -3
- package/srpc/packet-codec-vectors_test.go +195 -0
- package/srpc/packet-codec.test.ts +139 -0
- package/srpc/packet-rw.go +9 -2
- package/srpc/packet.cpp +60 -58
- package/srpc/packet.hpp +35 -20
- package/srpc/packet.rs +8 -27
- package/srpc/packet.ts +15 -2
- package/srpc/py.typed +0 -0
- package/srpc/rpc.rs +332 -115
- package/srpc/rpc_lifecycle_test.cc +333 -0
- package/srpc/rpcproto_pb2.py +40 -0
- package/srpc/rpcproto_pb2.pyi +40 -0
- package/srpc/server-cancel_test.go +39 -0
- package/srpc/server-rpc.cpp +83 -73
- package/srpc/server-rpc.go +21 -19
- package/srpc/server-rpc.hpp +56 -37
- package/srpc/server.rs +76 -131
- package/srpc/server.test.ts +50 -0
- package/srpc/server.ts +22 -6
- package/srpc/starpc.hpp +4 -2
- package/srpc/stream.go +4 -0
- package/srpc/stream.hpp +95 -34
- package/srpc/stream.rs +7 -2
- package/srpc/stream.test.ts +132 -1
- package/srpc/stream.ts +65 -9
- package/srpc/termination.test.ts +30 -0
- package/srpc/termination.ts +70 -0
- package/srpc/testing.rs +11 -66
- package/srpc/transport.rs +21 -71
- package/srpc/websocket.rs +106 -57
- package/srpc/writer.hpp +46 -32
- package/srpc/yamux.rs +82 -26
- package/srpc/tests/integration_test.rs +0 -495
package/srpc/server-rpc.go
CHANGED
|
@@ -6,15 +6,15 @@ import (
|
|
|
6
6
|
"github.com/pkg/errors"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
// ServerRPC
|
|
9
|
+
// ServerRPC runs one remote method and retains its resources until the handler exits.
|
|
10
10
|
type ServerRPC struct {
|
|
11
11
|
commonRPC
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
// invoker dispatches the selected service and method.
|
|
13
14
|
invoker Invoker
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
// NewServerRPC
|
|
17
|
-
// note: call SetWriter before handling any incoming messages.
|
|
17
|
+
// NewServerRPC attaches the handler and transport for one incoming call.
|
|
18
18
|
func NewServerRPC(ctx context.Context, invoker Invoker, writer PacketWriter) *ServerRPC {
|
|
19
19
|
rpc := &ServerRPC{invoker: invoker}
|
|
20
20
|
initCommonRPC(ctx, &rpc.commonRPC)
|
|
@@ -33,6 +33,7 @@ func (r *ServerRPC) HandlePacketData(data []byte) error {
|
|
|
33
33
|
|
|
34
34
|
// HandlePacket handles an incoming parsed message packet.
|
|
35
35
|
func (r *ServerRPC) HandlePacket(msg *Packet) error {
|
|
36
|
+
// Ignore an absent packet and reject malformed messages before dispatch.
|
|
36
37
|
if msg == nil {
|
|
37
38
|
return nil
|
|
38
39
|
}
|
|
@@ -40,6 +41,7 @@ func (r *ServerRPC) HandlePacket(msg *Packet) error {
|
|
|
40
41
|
return err
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// Route each packet through the shared stream state.
|
|
43
45
|
switch b := msg.GetBody().(type) {
|
|
44
46
|
case *Packet_CallStart:
|
|
45
47
|
return r.HandleCallStart(b.CallStart)
|
|
@@ -57,25 +59,20 @@ func (r *ServerRPC) HandlePacket(msg *Packet) error {
|
|
|
57
59
|
|
|
58
60
|
// HandleCallStart handles the call start packet.
|
|
59
61
|
func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// Accept one start before any terminal stream transition.
|
|
62
63
|
locked := r.bcast.Lock()
|
|
63
|
-
// process start: method and service
|
|
64
64
|
if r.method != "" || r.service != "" {
|
|
65
|
-
err = errors.New("call start must be sent only once")
|
|
66
65
|
locked.Unlock()
|
|
67
|
-
return
|
|
66
|
+
return errors.New("call start must be sent only once")
|
|
68
67
|
}
|
|
69
68
|
if r.dataClosed {
|
|
70
|
-
err = ErrCompleted
|
|
71
69
|
locked.Unlock()
|
|
72
|
-
return
|
|
70
|
+
return ErrCompleted
|
|
73
71
|
}
|
|
74
72
|
|
|
73
|
+
// Retain the request identity and optional first message for the handler.
|
|
75
74
|
service, method := pkt.GetRpcService(), pkt.GetRpcMethod()
|
|
76
75
|
r.service, r.method = service, method
|
|
77
|
-
|
|
78
|
-
// process first data packet, if included
|
|
79
76
|
if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
|
|
80
77
|
r.dataQueue = append(r.dataQueue, data)
|
|
81
78
|
}
|
|
@@ -84,27 +81,32 @@ func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
|
|
|
84
81
|
// Mark the method active before scheduling it so cancellation cannot make
|
|
85
82
|
// Wait return and release a mux while invokeRPC is still running user code.
|
|
86
83
|
r.localActive = true
|
|
87
|
-
|
|
88
|
-
// invoke the rpc
|
|
89
84
|
locked.Broadcast()
|
|
90
85
|
startServerRPCInvoke(func() {
|
|
91
86
|
r.invokeRPC(service, method)
|
|
92
87
|
})
|
|
93
88
|
locked.Unlock()
|
|
94
89
|
|
|
95
|
-
return
|
|
90
|
+
return nil
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
// invokeRPC invokes the RPC after CallStart is received.
|
|
99
94
|
func (r *ServerRPC) invokeRPC(serviceID, methodID string) {
|
|
100
|
-
//
|
|
95
|
+
// Run the selected handler before publishing its terminal result.
|
|
101
96
|
strm := NewMsgStream(r.ctx, r, r.cancelContext)
|
|
102
97
|
ok, err := r.invoker.InvokeMethod(serviceID, methodID, strm)
|
|
103
98
|
if err == nil && !ok {
|
|
104
99
|
err = ErrUnimplemented
|
|
105
100
|
}
|
|
106
|
-
|
|
101
|
+
|
|
102
|
+
// Cancellation has its own packet; a text error would lose its identity.
|
|
107
103
|
outPkt := NewCallDataPacket(nil, false, true, err)
|
|
108
|
-
|
|
104
|
+
if errors.Is(err, context.Canceled) {
|
|
105
|
+
outPkt = NewCallCancelPacket()
|
|
106
|
+
}
|
|
107
|
+
r.beginLocalCompletion()
|
|
108
|
+
if err := r.writer.WritePacket(outPkt); err != nil {
|
|
109
|
+
r.HandleStreamClose(err)
|
|
110
|
+
}
|
|
109
111
|
r.finishLocalCompletion()
|
|
110
112
|
}
|
package/srpc/server-rpc.hpp
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <thread>
|
|
5
|
-
|
|
6
3
|
#include "common-rpc.hpp"
|
|
7
4
|
#include "errors.hpp"
|
|
8
5
|
#include "invoker.hpp"
|
|
9
6
|
#include "msg-stream.hpp"
|
|
10
7
|
#include "writer.hpp"
|
|
11
8
|
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <thread>
|
|
11
|
+
|
|
12
12
|
namespace srpc {
|
|
13
13
|
class Packet;
|
|
14
14
|
class CallStart;
|
|
@@ -16,48 +16,67 @@ class CallStart;
|
|
|
16
16
|
|
|
17
17
|
namespace starpc {
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/*
|
|
20
|
+
* ServerRPC is the remote's side of one call: it runs the handler the
|
|
21
|
+
* Invoker selects and cancels and joins it on destruction. The Invoker and
|
|
22
|
+
* PacketWriter must outlive it. HandlePacket calls are serialized by the
|
|
23
|
+
* transport; handlers observe cancellation through Stream::StopToken.
|
|
24
|
+
*/
|
|
21
25
|
class ServerRPC : public CommonRPC, public MsgStreamRw {
|
|
22
26
|
public:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
/* ServerRPC serves one call through invoker, writing via writer. */
|
|
28
|
+
ServerRPC(Invoker *invoker, PacketWriter *writer);
|
|
29
|
+
~ServerRPC() override;
|
|
30
|
+
|
|
31
|
+
/* HandlePacketData parses one serialized packet and handles it. */
|
|
32
|
+
Error HandlePacketData(const std::string &data);
|
|
33
|
+
|
|
34
|
+
/* HandlePacket validates and dispatches one parsed packet. */
|
|
35
|
+
Error HandlePacket(const srpc::Packet &msg);
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* HandleCallStart accepts the call's first packet and runs the
|
|
39
|
+
* handler on its own thread.
|
|
40
|
+
*/
|
|
41
|
+
Error HandleCallStart(const srpc::CallStart &pkt);
|
|
42
|
+
|
|
43
|
+
/* MsgStreamRw over the shared call state. */
|
|
44
|
+
Error ReadOne(std::string *out) override
|
|
45
|
+
{
|
|
46
|
+
return CommonRPC::ReadOne(out);
|
|
47
|
+
}
|
|
48
|
+
Error WriteCallData(const std::string &data, bool data_is_zero,
|
|
49
|
+
bool complete, Error err) override
|
|
50
|
+
{
|
|
51
|
+
return CommonRPC::WriteCallData(data, data_is_zero, complete,
|
|
52
|
+
err);
|
|
53
|
+
}
|
|
54
|
+
Error WriteCallCancel() override
|
|
55
|
+
{
|
|
56
|
+
return CommonRPC::WriteCallCancel();
|
|
57
|
+
}
|
|
58
|
+
std::string RemoteErrorMessage() const override
|
|
59
|
+
{
|
|
60
|
+
return CommonRPC::RemoteErrorMessage();
|
|
61
|
+
}
|
|
46
62
|
|
|
47
63
|
private:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
/* InvokeRPC runs the handler and publishes its verdict. */
|
|
65
|
+
void InvokeRPC(const std::string &service_id,
|
|
66
|
+
const std::string &method_id);
|
|
67
|
+
|
|
68
|
+
/* invoker is borrowed; the caller owns it. */
|
|
69
|
+
Invoker *invoker;
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
/* invoke_thread is last so it stops before the state it reads. */
|
|
72
|
+
std::jthread invoke_thread;
|
|
54
73
|
};
|
|
55
74
|
|
|
56
|
-
|
|
57
|
-
// Matches Go NewServerRPC function in server-rpc.go
|
|
75
|
+
/* NewServerRPC constructs a ServerRPC for one call. */
|
|
58
76
|
inline std::unique_ptr<ServerRPC> NewServerRPC(Invoker *invoker,
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
PacketWriter *writer)
|
|
78
|
+
{
|
|
79
|
+
return std::make_unique<ServerRPC>(invoker, writer);
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
} // namespace starpc
|
package/srpc/server.rs
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
//! The server supports all streaming patterns: unary, client streaming,
|
|
5
5
|
//! server streaming, and bidirectional streaming.
|
|
6
6
|
|
|
7
|
-
use
|
|
8
|
-
use futures::StreamExt;
|
|
7
|
+
use futures::{stream::FuturesUnordered, StreamExt};
|
|
9
8
|
use std::sync::Arc;
|
|
10
9
|
use std::time::Duration;
|
|
11
10
|
use tokio::io::{AsyncRead, AsyncWrite};
|
|
@@ -128,86 +127,48 @@ impl<I: Invoker + 'static> Server<I> {
|
|
|
128
127
|
// Create framed reader.
|
|
129
128
|
let mut framed = FramedRead::new(read_half, PacketCodec::new());
|
|
130
129
|
|
|
131
|
-
// Wait for the first packet
|
|
130
|
+
// Wait for the first packet and take its CallStart body.
|
|
132
131
|
let first_packet = framed.next().await;
|
|
133
|
-
let
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
_ => return Err(Error::ExpectedCallStart),
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
Some(Err(e)) => return Err(e),
|
|
144
|
-
None => return Err(Error::StreamClosed),
|
|
132
|
+
let Some(Ok(packet)) = first_packet else {
|
|
133
|
+
// A decode error ends the stream; a closed transport has no error.
|
|
134
|
+
return first_packet.map_or(Err(Error::StreamClosed), |res| res.map(|_| ()));
|
|
135
|
+
};
|
|
136
|
+
packet.validate()?;
|
|
137
|
+
let Some(Body::CallStart(call_start)) = packet.body else {
|
|
138
|
+
return Err(Error::ExpectedCallStart);
|
|
145
139
|
};
|
|
146
140
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let service_id = call_start.rpc_service.clone();
|
|
157
|
-
let method_id = call_start.rpc_method.clone();
|
|
158
|
-
|
|
159
|
-
// Create the server RPC.
|
|
160
|
-
let ctx = Context::new();
|
|
161
|
-
let rpc = Arc::new(ServerRpc::from_call_start(ctx, call_start, writer));
|
|
141
|
+
// Retain the call's transport context until this borrowing operation ends.
|
|
142
|
+
let rpc = Arc::new(ServerRpc::from_call_start(
|
|
143
|
+
Context::new(),
|
|
144
|
+
call_start,
|
|
145
|
+
writer,
|
|
146
|
+
));
|
|
147
|
+
let _cancel = rpc.context().cancel_token().drop_guard_ref();
|
|
162
148
|
|
|
163
|
-
//
|
|
164
|
-
let
|
|
165
|
-
let mut read_task = tokio::spawn(async move {
|
|
149
|
+
// The packet reader is a sibling future, so dropping this call cannot detach transport work.
|
|
150
|
+
let read = async {
|
|
166
151
|
while let Some(result) = framed.next().await {
|
|
167
152
|
match result {
|
|
168
153
|
Ok(packet) => {
|
|
169
|
-
if
|
|
154
|
+
if rpc.handle_packet(packet).await.is_err() {
|
|
170
155
|
break;
|
|
171
156
|
}
|
|
172
157
|
}
|
|
173
158
|
Err(_) => break,
|
|
174
159
|
}
|
|
175
160
|
}
|
|
176
|
-
let _ =
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
let stream: Box<dyn Stream> = Box::new(ServerStream { rpc: rpc.clone() });
|
|
181
|
-
let (found, result) = self
|
|
182
|
-
.invoker
|
|
183
|
-
.invoke_method(&service_id, &method_id, stream)
|
|
184
|
-
.await;
|
|
185
|
-
|
|
186
|
-
// Handle the result.
|
|
187
|
-
if !found {
|
|
188
|
-
// Send unimplemented error.
|
|
189
|
-
let _ = rpc.send_error("method not implemented".to_string()).await;
|
|
190
|
-
} else if let Err(e) = result {
|
|
191
|
-
// Send error response.
|
|
192
|
-
let _ = rpc.send_error(e.to_string()).await;
|
|
193
|
-
} else {
|
|
194
|
-
// Close send side on success.
|
|
195
|
-
let _ = rpc.close_send().await;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Explicitly close the RPC to release the writer/transport.
|
|
199
|
-
// This ensures the connection doesn't remain open after the terminal response.
|
|
200
|
-
let _ = rpc.close().await;
|
|
161
|
+
let _ = rpc.handle_stream_close(None).await;
|
|
162
|
+
};
|
|
163
|
+
let invoke = serve_rpc(self.invoker.as_ref(), &rpc);
|
|
164
|
+
tokio::pin!(read, invoke);
|
|
201
165
|
|
|
202
|
-
//
|
|
203
|
-
//
|
|
166
|
+
// Remote closure cancels the same context that the active service receives.
|
|
167
|
+
// After a reply, retain the existing bounded grace period for the peer's transport close.
|
|
204
168
|
tokio::select! {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_ = tokio::time::sleep(self.config.shutdown_timeout) => {
|
|
209
|
-
// Timeout - abort the read task
|
|
210
|
-
read_task.abort();
|
|
169
|
+
() = &mut read => invoke.await,
|
|
170
|
+
() = &mut invoke => {
|
|
171
|
+
let _ = tokio::time::timeout(self.config.shutdown_timeout, read).await;
|
|
211
172
|
}
|
|
212
173
|
}
|
|
213
174
|
|
|
@@ -252,73 +213,59 @@ impl<I: Invoker + 'static> Server<I> {
|
|
|
252
213
|
.await
|
|
253
214
|
}
|
|
254
215
|
|
|
255
|
-
/// Accepts and handles connections
|
|
256
|
-
///
|
|
257
|
-
///
|
|
258
|
-
/// and spawns a task to handle each one.
|
|
259
|
-
///
|
|
260
|
-
/// Errors from individual connections are reported via the error handler
|
|
261
|
-
/// (if configured) but don't stop the server.
|
|
216
|
+
/// Accepts and handles connections concurrently, draining accepted calls when the listener ends.
|
|
217
|
+
/// Dropping this future drops every active call and cancels its transport context.
|
|
218
|
+
/// Individual connection failures reach the error handler without stopping other calls.
|
|
262
219
|
pub async fn serve<L, T>(&self, mut listener: L) -> Result<()>
|
|
263
220
|
where
|
|
264
221
|
L: futures::Stream<Item = std::io::Result<T>> + Unpin,
|
|
265
222
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
266
223
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
224
|
+
// Concurrent call futures remain owned by this invocation, without detached tasks.
|
|
225
|
+
let mut calls = FuturesUnordered::new();
|
|
226
|
+
let mut accepting = true;
|
|
227
|
+
loop {
|
|
228
|
+
tokio::select! {
|
|
229
|
+
connection = listener.next(), if accepting => {
|
|
230
|
+
match connection {
|
|
231
|
+
Some(Ok(stream)) => calls.push(async move {
|
|
232
|
+
if let Err(error) = self.handle_stream(stream).await {
|
|
233
|
+
self.report_error(error);
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
Some(Err(error)) => self.report_error(Error::Io(error)),
|
|
237
|
+
None => accepting = false,
|
|
238
|
+
}
|
|
279
239
|
}
|
|
240
|
+
Some(()) = calls.next(), if !calls.is_empty() => {}
|
|
241
|
+
else => return Ok(()),
|
|
280
242
|
}
|
|
281
243
|
}
|
|
282
|
-
|
|
283
|
-
Ok(())
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/// Creates a clone of the server for spawning tasks.
|
|
287
|
-
pub(crate) fn clone_for_spawn(&self) -> Server<I> {
|
|
288
|
-
Server {
|
|
289
|
-
invoker: self.invoker.clone(),
|
|
290
|
-
config: self.config.clone(),
|
|
291
|
-
error_handler: self.error_handler.clone(),
|
|
292
|
-
}
|
|
293
244
|
}
|
|
294
245
|
}
|
|
295
246
|
|
|
296
|
-
///
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async fn close(&self) -> Result<()> {
|
|
320
|
-
self.rpc.close().await
|
|
321
|
-
}
|
|
247
|
+
/// Invokes the method a server RPC names and settles the call with the outcome.
|
|
248
|
+
///
|
|
249
|
+
/// An unknown method or a handler error reaches the peer as an error, and a
|
|
250
|
+
/// handler that returns cleanly closes the send side. The RPC is closed last,
|
|
251
|
+
/// which releases its writer.
|
|
252
|
+
pub(crate) async fn serve_rpc<I: Invoker + ?Sized>(invoker: &I, rpc: &Arc<ServerRpc>) {
|
|
253
|
+
// Invoke the method with the RPC as its stream.
|
|
254
|
+
let (found, result) = invoker
|
|
255
|
+
.invoke_method(rpc.service(), rpc.method(), Box::new(rpc.clone()))
|
|
256
|
+
.await;
|
|
257
|
+
|
|
258
|
+
// Report the outcome to the peer; a failed write leaves nothing to settle.
|
|
259
|
+
let _ = if !found {
|
|
260
|
+
rpc.send_error("method not implemented".to_string()).await
|
|
261
|
+
} else if let Err(err) = result {
|
|
262
|
+
rpc.send_error(err.to_string()).await
|
|
263
|
+
} else {
|
|
264
|
+
rpc.close_send().await
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Close the RPC so the connection does not stay open after the terminal response.
|
|
268
|
+
let _ = rpc.close().await;
|
|
322
269
|
}
|
|
323
270
|
|
|
324
271
|
#[cfg(test)]
|
|
@@ -330,10 +277,9 @@ mod tests {
|
|
|
330
277
|
#[tokio::test]
|
|
331
278
|
async fn test_server_config() {
|
|
332
279
|
let mux = Mux::new();
|
|
333
|
-
let server = Server::new(mux)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
});
|
|
280
|
+
let server = Server::new(mux).with_config(ServerConfig {
|
|
281
|
+
shutdown_timeout: Duration::from_secs(1),
|
|
282
|
+
});
|
|
337
283
|
|
|
338
284
|
assert_eq!(server.config.shutdown_timeout, Duration::from_secs(1));
|
|
339
285
|
}
|
|
@@ -346,10 +292,9 @@ mod tests {
|
|
|
346
292
|
let errors_clone = errors.clone();
|
|
347
293
|
|
|
348
294
|
let mux = Mux::new();
|
|
349
|
-
let server = Server::new(mux)
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
});
|
|
295
|
+
let server = Server::new(mux).with_error_handler(move |e| {
|
|
296
|
+
errors_clone.lock().unwrap().push(e.to_string());
|
|
297
|
+
});
|
|
353
298
|
|
|
354
299
|
// Report an error
|
|
355
300
|
server.report_error(Error::StreamClosed);
|
package/srpc/server.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, beforeEach, expect, vi } from 'vitest'
|
|
2
2
|
import { pipe } from 'it-pipe'
|
|
3
|
+
import { pushable } from 'it-pushable'
|
|
4
|
+
import type { Source } from 'it-stream-types'
|
|
3
5
|
import {
|
|
4
6
|
createHandler,
|
|
5
7
|
createMux,
|
|
@@ -175,6 +177,8 @@ describe('srpc server', () => {
|
|
|
175
177
|
const server = new Server(mux.lookupMethod)
|
|
176
178
|
const firstResponse = new Promise<Packet>((resolve, reject) => {
|
|
177
179
|
server.handlePacketStream({
|
|
180
|
+
close: async () => {},
|
|
181
|
+
abort: () => {},
|
|
178
182
|
source: (async function* () {
|
|
179
183
|
yield Packet.toBinary({
|
|
180
184
|
body: {
|
|
@@ -342,6 +346,52 @@ describe('srpc server', () => {
|
|
|
342
346
|
await Promise.resolve()
|
|
343
347
|
})
|
|
344
348
|
|
|
349
|
+
it('closes the packet stream when the server pipeline completes', async () => {
|
|
350
|
+
const server = new Server(createMux().lookupMethod)
|
|
351
|
+
const close = vi.fn(async () => {})
|
|
352
|
+
const source = pushable<Uint8Array>({ objectMode: true })
|
|
353
|
+
const stream = {
|
|
354
|
+
close,
|
|
355
|
+
abort: vi.fn(),
|
|
356
|
+
source,
|
|
357
|
+
sink: async (output: Source<Uint8Array>) => {
|
|
358
|
+
for await (const _packet of output) {
|
|
359
|
+
// Drain the response pipeline.
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
const rpc = server.handlePacketStream(stream)
|
|
364
|
+
|
|
365
|
+
await rpc.close()
|
|
366
|
+
source.end()
|
|
367
|
+
|
|
368
|
+
await vi.waitFor(() => expect(close).toHaveBeenCalledOnce())
|
|
369
|
+
expect(stream.abort).not.toHaveBeenCalled()
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('aborts the packet stream when the server pipeline fails', async () => {
|
|
373
|
+
const server = new Server(createMux().lookupMethod)
|
|
374
|
+
const error = new Error('input failed')
|
|
375
|
+
const abort = vi.fn()
|
|
376
|
+
const source = pushable<Uint8Array>({ objectMode: true })
|
|
377
|
+
const stream = {
|
|
378
|
+
close: vi.fn(async () => {}),
|
|
379
|
+
abort,
|
|
380
|
+
source,
|
|
381
|
+
sink: async (output: Source<Uint8Array>) => {
|
|
382
|
+
for await (const _packet of output) {
|
|
383
|
+
// Drain the response pipeline.
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
server.handlePacketStream(stream)
|
|
389
|
+
source.end(error)
|
|
390
|
+
|
|
391
|
+
await vi.waitFor(() => expect(abort).toHaveBeenCalledWith(error))
|
|
392
|
+
expect(stream.close).not.toHaveBeenCalled()
|
|
393
|
+
})
|
|
394
|
+
|
|
345
395
|
it('tears down passive channel close state', async () => {
|
|
346
396
|
const { port1, port2 } = new MessageChannel()
|
|
347
397
|
const opts: ChannelStreamOpts = { idleTimeoutMs: 1000, keepAliveMs: 1000 }
|
package/srpc/server.ts
CHANGED
|
@@ -20,9 +20,7 @@ export class Server implements StreamHandler {
|
|
|
20
20
|
public get rpcStreamHandler(): HandleStreamFunc {
|
|
21
21
|
return async (stream: PacketStream) => {
|
|
22
22
|
const rpc = this.startRpc()
|
|
23
|
-
return
|
|
24
|
-
.catch((err: Error) => rpc.close(err))
|
|
25
|
-
.then(() => rpc.close())
|
|
23
|
+
return runPacketStream(stream, rpc)
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
26
|
|
|
@@ -36,9 +34,27 @@ export class Server implements StreamHandler {
|
|
|
36
34
|
// the stream has one Uint8Array per packet w/o length prefix.
|
|
37
35
|
public handlePacketStream(stream: PacketStream): ServerRPC {
|
|
38
36
|
const rpc = this.startRpc()
|
|
39
|
-
|
|
40
|
-
.catch((err: Error) => rpc.close(err))
|
|
41
|
-
.then(() => rpc.close())
|
|
37
|
+
void runPacketStream(stream, rpc).catch(() => undefined)
|
|
42
38
|
return rpc
|
|
43
39
|
}
|
|
44
40
|
}
|
|
41
|
+
|
|
42
|
+
async function runPacketStream(
|
|
43
|
+
stream: PacketStream,
|
|
44
|
+
rpc: ServerRPC,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
await pipe(stream, decodePacketSource, rpc, encodePacketSource, stream)
|
|
48
|
+
if (rpc.isClosed instanceof Error) {
|
|
49
|
+
stream.abort(rpc.isClosed)
|
|
50
|
+
throw rpc.isClosed
|
|
51
|
+
}
|
|
52
|
+
await stream.close()
|
|
53
|
+
await rpc.close()
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const error = err instanceof Error ? err : new Error(String(err))
|
|
56
|
+
stream.abort(error)
|
|
57
|
+
await rpc.close(error)
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
60
|
+
}
|
package/srpc/starpc.hpp
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/*
|
|
4
|
+
* starpc.hpp is the library's umbrella header: include it for the whole
|
|
5
|
+
* hand-written C++ API.
|
|
6
|
+
*/
|
|
5
7
|
|
|
6
8
|
#include "client-rpc.hpp"
|
|
7
9
|
#include "client.hpp"
|
package/srpc/stream.go
CHANGED
|
@@ -17,6 +17,10 @@ type Stream interface {
|
|
|
17
17
|
MsgRecv(msg Message) error
|
|
18
18
|
|
|
19
19
|
// CloseSend signals to the remote that we will no longer send any messages.
|
|
20
|
+
//
|
|
21
|
+
// An error means the signal was not written, usually because the call
|
|
22
|
+
// already ended. MsgRecv still returns every message received before the
|
|
23
|
+
// end, then the call's outcome.
|
|
20
24
|
CloseSend() error
|
|
21
25
|
|
|
22
26
|
// Close closes the stream for reading and writing.
|