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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
package srpc_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
"net"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/aperturerobotics/starpc/echo"
|
|
10
|
+
"github.com/aperturerobotics/starpc/srpc"
|
|
11
|
+
"github.com/pkg/errors"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// endedClient returns each new stream only after its transport has closed, so
|
|
15
|
+
// the generated caller half-closes after the remote already ended the call.
|
|
16
|
+
type endedClient struct {
|
|
17
|
+
srpc.Client
|
|
18
|
+
// closed is closed after the transport close callback of the one stream
|
|
19
|
+
// this client may open returns.
|
|
20
|
+
closed <-chan struct{}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// NewStream opens the stream and waits for its transport to close.
|
|
24
|
+
func (c *endedClient) NewStream(
|
|
25
|
+
ctx context.Context,
|
|
26
|
+
service, method string,
|
|
27
|
+
firstMsg srpc.Message,
|
|
28
|
+
) (srpc.Stream, error) {
|
|
29
|
+
strm, err := c.Client.NewStream(ctx, service, method, firstMsg)
|
|
30
|
+
if err != nil {
|
|
31
|
+
return nil, err
|
|
32
|
+
}
|
|
33
|
+
<-c.closed
|
|
34
|
+
return strm, nil
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// onceEchoServer answers a server stream with one message and returns.
|
|
38
|
+
type onceEchoServer struct {
|
|
39
|
+
*echo.EchoServer
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// EchoServerStream sends the request back once and completes the call.
|
|
43
|
+
func (onceEchoServer) EchoServerStream(msg *echo.EchoMsg, strm echo.SRPCEchoer_EchoServerStreamStream) error {
|
|
44
|
+
return strm.Send(msg)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// newEchoPipe serves impl over an in-memory pipe per stream.
|
|
48
|
+
func newEchoPipe(t *testing.T, impl echo.SRPCEchoerServer) srpc.OpenStreamFunc {
|
|
49
|
+
t.Helper()
|
|
50
|
+
mux := srpc.NewMux()
|
|
51
|
+
if err := echo.SRPCRegisterEchoer(mux, impl); err != nil {
|
|
52
|
+
t.Fatal(err)
|
|
53
|
+
}
|
|
54
|
+
return srpc.NewServerPipe(srpc.NewServer(mux))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// notifyClose wraps openStream for a single stream and returns a channel
|
|
58
|
+
// closed after that stream's transport close callback returns.
|
|
59
|
+
func notifyClose(openStream srpc.OpenStreamFunc) (srpc.OpenStreamFunc, <-chan struct{}) {
|
|
60
|
+
closed := make(chan struct{})
|
|
61
|
+
return func(
|
|
62
|
+
ctx context.Context,
|
|
63
|
+
msgHandler srpc.PacketDataHandler,
|
|
64
|
+
closeHandler srpc.CloseHandler,
|
|
65
|
+
) (srpc.PacketWriter, error) {
|
|
66
|
+
return openStream(ctx, msgHandler, func(closeErr error) {
|
|
67
|
+
closeHandler(closeErr)
|
|
68
|
+
close(closed)
|
|
69
|
+
})
|
|
70
|
+
}, closed
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestServerStreamCompletedBeforeCloseSend(t *testing.T) {
|
|
74
|
+
// Complete the server stream before the generated caller half-closes.
|
|
75
|
+
openStream, closed := notifyClose(newEchoPipe(t, onceEchoServer{echo.NewEchoServer(nil)}))
|
|
76
|
+
client := echo.NewSRPCEchoerClient(&endedClient{Client: srpc.NewClient(openStream), closed: closed})
|
|
77
|
+
strm, err := client.EchoServerStream(t.Context(), &echo.EchoMsg{Body: bodyTxt})
|
|
78
|
+
if err != nil {
|
|
79
|
+
t.Fatalf("server stream discarded after remote completion: %v", err)
|
|
80
|
+
}
|
|
81
|
+
t.Cleanup(func() { _ = strm.Close() })
|
|
82
|
+
|
|
83
|
+
// Read the buffered response, then the clean completion.
|
|
84
|
+
msg, err := strm.Recv()
|
|
85
|
+
if err != nil {
|
|
86
|
+
t.Fatalf("buffered response lost: %v", err)
|
|
87
|
+
}
|
|
88
|
+
if msg.GetBody() != bodyTxt {
|
|
89
|
+
t.Fatalf("expected %q got %q", bodyTxt, msg.GetBody())
|
|
90
|
+
}
|
|
91
|
+
if _, err := strm.Recv(); err != io.EOF {
|
|
92
|
+
t.Fatalf("expected io.EOF after completion, got %v", err)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func TestClientStreamCompletedBeforeCloseSend(t *testing.T) {
|
|
97
|
+
// EchoClientStream replies to the first message without waiting for the
|
|
98
|
+
// half-close, so the call ends while the client may still send.
|
|
99
|
+
openStream, closed := notifyClose(newEchoPipe(t, echo.NewEchoServer(nil)))
|
|
100
|
+
client := echo.NewSRPCEchoerClient(srpc.NewClient(openStream))
|
|
101
|
+
strm, err := client.EchoClientStream(t.Context())
|
|
102
|
+
if err != nil {
|
|
103
|
+
t.Fatal(err)
|
|
104
|
+
}
|
|
105
|
+
t.Cleanup(func() { _ = strm.Close() })
|
|
106
|
+
|
|
107
|
+
// Send the one request and wait for the call to end.
|
|
108
|
+
if err := strm.Send(&echo.EchoMsg{Body: bodyTxt}); err != nil {
|
|
109
|
+
t.Fatal(err)
|
|
110
|
+
}
|
|
111
|
+
<-closed
|
|
112
|
+
|
|
113
|
+
// The half-close fails, but the reply is still readable.
|
|
114
|
+
msg, err := strm.CloseAndRecv()
|
|
115
|
+
if err != nil {
|
|
116
|
+
t.Fatalf("buffered reply lost: %v", err)
|
|
117
|
+
}
|
|
118
|
+
if msg.GetBody() != bodyTxt {
|
|
119
|
+
t.Fatalf("expected %q got %q", bodyTxt, msg.GetBody())
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func TestServerStreamTransportFailureBeforeCloseSend(t *testing.T) {
|
|
124
|
+
// Accept CallStart, then drop the transport without a response.
|
|
125
|
+
openStream, closed := notifyClose(func(
|
|
126
|
+
ctx context.Context,
|
|
127
|
+
msgHandler srpc.PacketDataHandler,
|
|
128
|
+
closeHandler srpc.CloseHandler,
|
|
129
|
+
) (srpc.PacketWriter, error) {
|
|
130
|
+
srvPipe, clientPipe := net.Pipe()
|
|
131
|
+
srvPrw := srpc.NewPacketReadWriter(srvPipe)
|
|
132
|
+
go func() {
|
|
133
|
+
_ = srvPrw.ReadToHandler(func([]byte) error {
|
|
134
|
+
return srvPrw.Close()
|
|
135
|
+
})
|
|
136
|
+
}()
|
|
137
|
+
clientPrw := srpc.NewPacketReadWriter(clientPipe)
|
|
138
|
+
go clientPrw.ReadPump(msgHandler, closeHandler)
|
|
139
|
+
return clientPrw, nil
|
|
140
|
+
})
|
|
141
|
+
client := echo.NewSRPCEchoerClient(&endedClient{Client: srpc.NewClient(openStream), closed: closed})
|
|
142
|
+
strm, err := client.EchoServerStream(t.Context(), &echo.EchoMsg{Body: bodyTxt})
|
|
143
|
+
if err != nil {
|
|
144
|
+
t.Fatalf("server stream discarded after transport failure: %v", err)
|
|
145
|
+
}
|
|
146
|
+
t.Cleanup(func() { _ = strm.Close() })
|
|
147
|
+
|
|
148
|
+
// Recv reports that the call ended without a verdict.
|
|
149
|
+
if _, err := strm.Recv(); !errors.Is(err, srpc.ErrClosedBeforeCompletion) {
|
|
150
|
+
t.Fatalf("expected ErrClosedBeforeCompletion, got %v", err)
|
|
151
|
+
}
|
|
152
|
+
}
|
package/srpc/client-invoker.go
CHANGED
|
@@ -17,65 +17,55 @@ func NewClientInvoker(client Client) *ClientInvoker {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// InvokeMethod invokes the method by proxying to the remote via the client.
|
|
20
|
-
//
|
|
20
|
+
// The remote's terminal result, including its error, is the result of the
|
|
21
|
+
// call. Returns false, nil if the client is nil.
|
|
21
22
|
func (c *ClientInvoker) InvokeMethod(serviceID, methodID string, strm Stream) (bool, error) {
|
|
22
23
|
if c.client == nil {
|
|
23
24
|
return false, nil
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Open a stream to the remote
|
|
29
|
-
remoteStrm, err := c.client.NewStream(ctx, serviceID, methodID, nil)
|
|
27
|
+
// Open a stream to the remote.
|
|
28
|
+
remoteStrm, err := c.client.NewStream(strm.Context(), serviceID, methodID, nil)
|
|
30
29
|
if err != nil {
|
|
31
30
|
return true, err
|
|
32
31
|
}
|
|
33
32
|
defer remoteStrm.Close()
|
|
34
33
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
go proxyStreamTo(strm, remoteStrm, errCh)
|
|
38
|
-
go proxyStreamTo(remoteStrm, strm, errCh)
|
|
34
|
+
// Forward the caller's messages until it half-closes or fails.
|
|
35
|
+
go forwardRequests(strm, remoteStrm)
|
|
39
36
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
// Forward the remote's messages; the server publishes the result to the
|
|
38
|
+
// caller after this returns.
|
|
39
|
+
return true, forwardMessages(remoteStrm, strm)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// forwardRequests copies the caller's messages to the remote, then
|
|
43
|
+
// half-closes the remote, or closes it when the caller fails.
|
|
44
|
+
func forwardRequests(caller, remote Stream) {
|
|
45
|
+
if err := forwardMessages(caller, remote); err != nil {
|
|
46
|
+
_ = remote.Close()
|
|
47
|
+
return
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
_ = remote.CloseSend()
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Forward all messages including empty ones (valid for empty proto messages)
|
|
60
|
-
err = dst.MsgSend(pkt)
|
|
61
|
-
pkt.Clear()
|
|
62
|
-
if err != nil {
|
|
63
|
-
return err
|
|
52
|
+
// forwardMessages copies messages from src to dst until src ends. It returns
|
|
53
|
+
// nil when src half-closes.
|
|
54
|
+
func forwardMessages(src, dst Stream) error {
|
|
55
|
+
// Forward all messages including empty ones, which are valid empty protos.
|
|
56
|
+
pkt := NewRawMessage(nil, true)
|
|
57
|
+
for {
|
|
58
|
+
if err := src.MsgRecv(pkt); err != nil {
|
|
59
|
+
if err == io.EOF {
|
|
60
|
+
return nil
|
|
64
61
|
}
|
|
62
|
+
return err
|
|
65
63
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
errCh <- rerr
|
|
64
|
+
err := dst.MsgSend(pkt)
|
|
65
|
+
pkt.Clear()
|
|
66
|
+
if err != nil {
|
|
67
|
+
return err
|
|
71
68
|
}
|
|
72
|
-
_ = dst.Close()
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
rerr = dst.CloseSend()
|
|
77
|
-
if errCh != nil {
|
|
78
|
-
errCh <- rerr
|
|
79
69
|
}
|
|
80
70
|
}
|
|
81
71
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package srpc_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"testing"
|
|
6
|
+
|
|
7
|
+
"github.com/aperturerobotics/starpc/echo"
|
|
8
|
+
"github.com/aperturerobotics/starpc/srpc"
|
|
9
|
+
"github.com/pkg/errors"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// failingEchoServer fails every unary echo.
|
|
13
|
+
type failingEchoServer struct {
|
|
14
|
+
*echo.EchoServer
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Echo returns errEchoFailed.
|
|
18
|
+
func (failingEchoServer) Echo(context.Context, *echo.EchoMsg) (*echo.EchoMsg, error) {
|
|
19
|
+
return nil, errEchoFailed
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// errEchoFailed is the error failingEchoServer returns.
|
|
23
|
+
var errEchoFailed = errors.New("echo failed")
|
|
24
|
+
|
|
25
|
+
// TestClientInvokerForwardsResult checks that a proxied call reports the
|
|
26
|
+
// remote's reply and the remote handler's error.
|
|
27
|
+
func TestClientInvokerForwardsResult(t *testing.T) {
|
|
28
|
+
ctx := t.Context()
|
|
29
|
+
proxy := func(server echo.SRPCEchoerServer) echo.SRPCEchoerClient {
|
|
30
|
+
mux := srpc.NewMux()
|
|
31
|
+
if err := echo.SRPCRegisterEchoer(mux, server); err != nil {
|
|
32
|
+
t.Fatal(err)
|
|
33
|
+
}
|
|
34
|
+
remote := srpc.NewClient(srpc.NewServerPipe(srpc.NewServer(mux)))
|
|
35
|
+
invoker := srpc.NewClientInvoker(remote)
|
|
36
|
+
return echo.NewSRPCEchoerClient(srpc.NewClient(srpc.NewServerPipe(srpc.NewServer(invoker))))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A reply passes through unchanged.
|
|
40
|
+
reply, err := proxy(echo.NewEchoServer(nil)).Echo(ctx, &echo.EchoMsg{Body: "hello"})
|
|
41
|
+
if err != nil {
|
|
42
|
+
t.Fatal(err)
|
|
43
|
+
}
|
|
44
|
+
if reply.GetBody() != "hello" {
|
|
45
|
+
t.Errorf("reply body = %q", reply.GetBody())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// A handler error reaches the caller instead of a cancellation.
|
|
49
|
+
_, err = proxy(failingEchoServer{}).Echo(ctx, &echo.EchoMsg{Body: "hello"})
|
|
50
|
+
if err == nil || err.Error() != errEchoFailed.Error() {
|
|
51
|
+
t.Errorf("proxied error = %v, want %v", err, errEchoFailed)
|
|
52
|
+
}
|
|
53
|
+
}
|
package/srpc/client-rpc.cpp
CHANGED
|
@@ -6,101 +6,86 @@
|
|
|
6
6
|
|
|
7
7
|
namespace starpc {
|
|
8
8
|
|
|
9
|
-
ClientRPC::ClientRPC(const std::string &service, const std::string &method)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
ClientRPC::ClientRPC(const std::string &service, const std::string &method)
|
|
10
|
+
{
|
|
11
|
+
this->service = service;
|
|
12
|
+
this->method = method;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
ClientRPC::~ClientRPC() = default;
|
|
16
16
|
|
|
17
17
|
Error ClientRPC::Start(PacketWriter *writer, bool write_first_msg,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cv_.notify_all();
|
|
50
|
-
return err;
|
|
18
|
+
const std::string &first_msg)
|
|
19
|
+
{
|
|
20
|
+
if (writer == nullptr)
|
|
21
|
+
return Error::NilWriter;
|
|
22
|
+
if (service.empty())
|
|
23
|
+
return Error::EmptyServiceID;
|
|
24
|
+
if (method.empty())
|
|
25
|
+
return Error::EmptyMethodID;
|
|
26
|
+
|
|
27
|
+
std::unique_lock writing(write_mutex);
|
|
28
|
+
{
|
|
29
|
+
std::lock_guard lock(state_mutex);
|
|
30
|
+
if (this->writer != nullptr)
|
|
31
|
+
return Error::Completed;
|
|
32
|
+
this->writer = writer;
|
|
33
|
+
if (canceled || data_closed) {
|
|
34
|
+
writing.unlock();
|
|
35
|
+
/* The caller closes a rejected writer. */
|
|
36
|
+
return Error::Canceled;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const auto packet = NewCallStartPacket(
|
|
41
|
+
service, method, write_first_msg ? first_msg : "",
|
|
42
|
+
write_first_msg && first_msg.empty());
|
|
43
|
+
const auto written = writer->WritePacket(*packet);
|
|
44
|
+
writing.unlock();
|
|
45
|
+
if (written != Error::OK)
|
|
46
|
+
HandleStreamClose(written);
|
|
47
|
+
return written;
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
Error ClientRPC::HandlePacketData(const std::string &data)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
Error ClientRPC::HandlePacketData(const std::string &data)
|
|
51
|
+
{
|
|
52
|
+
srpc::Packet packet;
|
|
53
|
+
if (!packet.ParseFromString(data))
|
|
54
|
+
return Error::InvalidMessage;
|
|
55
|
+
return HandlePacket(packet);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
void ClientRPC::HandleStreamClose(Error close_err)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
remote_err_ = close_err;
|
|
65
|
-
}
|
|
66
|
-
data_closed_ = true;
|
|
67
|
-
Cancel();
|
|
68
|
-
cv_.notify_all();
|
|
58
|
+
void ClientRPC::HandleStreamClose(Error close_err)
|
|
59
|
+
{
|
|
60
|
+
CommonRPC::HandleStreamClose(close_err);
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
Error ClientRPC::HandlePacket(const srpc::Packet &
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
default:
|
|
88
|
-
return Error::OK;
|
|
89
|
-
}
|
|
63
|
+
Error ClientRPC::HandlePacket(const srpc::Packet &packet)
|
|
64
|
+
{
|
|
65
|
+
const auto err = ValidatePacket(packet);
|
|
66
|
+
if (err != Error::OK)
|
|
67
|
+
return err;
|
|
68
|
+
|
|
69
|
+
switch (packet.body_case()) {
|
|
70
|
+
case srpc::Packet::kCallStart:
|
|
71
|
+
return Error::UnrecognizedPacket;
|
|
72
|
+
case srpc::Packet::kCallData:
|
|
73
|
+
return HandleCallData(packet.call_data());
|
|
74
|
+
case srpc::Packet::kCallCancel:
|
|
75
|
+
return packet.call_cancel() ? HandleCallCancel() : Error::OK;
|
|
76
|
+
default:
|
|
77
|
+
return Error::UnrecognizedPacket;
|
|
78
|
+
}
|
|
90
79
|
}
|
|
91
80
|
|
|
92
|
-
Error ClientRPC::HandleCallStart(const srpc::CallStart &
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
Error ClientRPC::HandleCallStart(const srpc::CallStart &)
|
|
82
|
+
{
|
|
83
|
+
return Error::UnrecognizedPacket;
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
void ClientRPC::Close()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (writer_ != nullptr) {
|
|
101
|
-
WriteCallCancelLocked();
|
|
102
|
-
CloseLocked();
|
|
103
|
-
}
|
|
86
|
+
void ClientRPC::Close()
|
|
87
|
+
{
|
|
88
|
+
Cancel();
|
|
104
89
|
}
|
|
105
90
|
|
|
106
91
|
} // namespace starpc
|
package/srpc/client-rpc.go
CHANGED
|
@@ -11,10 +11,8 @@ type ClientRPC struct {
|
|
|
11
11
|
commonRPC
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// NewClientRPC constructs a
|
|
15
|
-
//
|
|
16
|
-
// service and method must be specified.
|
|
17
|
-
// must call Start after creating the RPC object.
|
|
14
|
+
// NewClientRPC constructs a call with the given service and method.
|
|
15
|
+
// Start attaches its transport; Close releases it.
|
|
18
16
|
func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
|
|
19
17
|
rpc := &ClientRPC{}
|
|
20
18
|
initCommonRPC(ctx, &rpc.commonRPC)
|
|
@@ -23,39 +21,42 @@ func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
|
|
|
23
21
|
return rpc
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
// Start
|
|
27
|
-
// must only be called once!
|
|
24
|
+
// Start attaches one writer and sends CallStart without holding the state lock.
|
|
28
25
|
func (r *ClientRPC) Start(writer PacketWriter, writeFirstMsg bool, firstMsg []byte) error {
|
|
26
|
+
// Validate the call before transferring the transport to it.
|
|
29
27
|
if writer == nil {
|
|
30
28
|
return ErrNilWriter
|
|
31
29
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
r.cancelContext()
|
|
35
|
-
_ = writer.Close()
|
|
36
|
-
return context.Canceled
|
|
30
|
+
if !writeFirstMsg {
|
|
31
|
+
firstMsg = nil
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
locked := r.bcast.Lock()
|
|
42
|
-
r.writer = writer
|
|
43
|
-
|
|
44
|
-
if writeFirstMsg {
|
|
45
|
-
firstMsgEmpty = len(firstMsg) == 0
|
|
33
|
+
pkt := NewCallStartPacket(r.service, r.method, firstMsg, writeFirstMsg && len(firstMsg) == 0)
|
|
34
|
+
if err := pkt.Validate(); err != nil {
|
|
35
|
+
return err
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
// Publish the writer before transport callbacks can deliver a response.
|
|
39
|
+
locked := r.bcast.Lock()
|
|
40
|
+
if r.writer != nil {
|
|
41
|
+
locked.Unlock()
|
|
42
|
+
return ErrCompleted
|
|
53
43
|
}
|
|
54
|
-
|
|
44
|
+
r.writer = writer
|
|
55
45
|
locked.Broadcast()
|
|
56
46
|
locked.Unlock()
|
|
57
47
|
|
|
58
|
-
|
|
48
|
+
// Cancellation closes the transport even when its first write is blocked.
|
|
49
|
+
stop := context.AfterFunc(r.ctx, r.Close)
|
|
50
|
+
defer stop()
|
|
51
|
+
if err := r.ctx.Err(); err != nil {
|
|
52
|
+
r.Close()
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
if err := writer.WritePacket(pkt); err != nil {
|
|
56
|
+
r.Close()
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
return nil
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
// HandlePacketData handles an incoming unparsed message packet.
|
|
@@ -95,20 +96,18 @@ func (r *ClientRPC) HandlePacket(msg *Packet) error {
|
|
|
95
96
|
|
|
96
97
|
// HandleCallStart handles the call start packet.
|
|
97
98
|
func (r *ClientRPC) HandleCallStart(pkt *CallStart) error {
|
|
98
|
-
// server-to-client calls not supported
|
|
99
99
|
return errors.Wrap(ErrUnrecognizedPacket, "call start packet unexpected")
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Close releases any resources held by the ClientRPC.
|
|
103
103
|
func (r *ClientRPC) Close() {
|
|
104
|
+
// Settle the call before releasing a transport that can call back into it.
|
|
104
105
|
locked := r.bcast.Lock()
|
|
105
|
-
|
|
106
|
-
// call did not start yet if writer is nil.
|
|
107
|
-
if r.writer != nil {
|
|
108
|
-
_ = r.WriteCallCancel()
|
|
109
|
-
writer = r.closeLocked(&locked)
|
|
110
|
-
}
|
|
106
|
+
writer := r.closeLocked(&locked)
|
|
111
107
|
locked.Unlock()
|
|
108
|
+
|
|
109
|
+
// Closing the transport interrupts pending writes; sending cancellation first
|
|
110
|
+
// could itself block forever behind a peer that stopped reading.
|
|
112
111
|
if writer != nil {
|
|
113
112
|
_ = writer.Close()
|
|
114
113
|
}
|