starpc 0.50.0 → 0.51.0
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/dist/integration/cross-language/ts-client.js +5 -79
- package/dist/integration/cross-language/ts-server.js +4 -100
- package/dist/srpc/client.d.ts +1 -3
- package/dist/srpc/client.js +2 -22
- package/dist/srpc/common-rpc.d.ts +1 -9
- package/dist/srpc/common-rpc.js +4 -62
- package/dist/srpc/handler.d.ts +1 -2
- package/dist/srpc/index.d.ts +0 -4
- package/dist/srpc/index.js +0 -3
- package/dist/srpc/invoker.d.ts +1 -2
- package/dist/srpc/rpcproto.pb.d.ts +0 -44
- package/dist/srpc/rpcproto.pb.js +0 -53
- package/dist/srpc/server-rpc.js +1 -3
- package/dist/srpc/server.test.js +35 -1
- package/echo/echo.go +8 -8
- package/echo/echo.pb.go +12 -36
- package/echo/echo_srpc.pb.cpp +1 -1
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/echo_srpc.pb.hpp +1 -1
- package/echo/echo_srpc.pb.rs +1 -1
- package/echo/server.go +1 -1
- package/go.mod +1 -1
- package/go.sum +2 -0
- package/integration/cross-language/go-client/main.go +4 -142
- package/integration/cross-language/go-server/main.go +2 -170
- package/integration/cross-language/run.bash +5 -105
- package/integration/cross-language/ts-client.ts +5 -90
- package/integration/cross-language/ts-server.ts +4 -112
- package/mock/mock.go +1 -1
- package/mock/mock.pb.go +12 -36
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +1 -1
- package/package.json +1 -1
- package/srpc/client-prefix.go +1 -1
- package/srpc/client-set.go +1 -1
- package/srpc/client.go +1 -1
- package/srpc/client.ts +4 -31
- package/srpc/common-rpc.go +15 -69
- package/srpc/common-rpc.ts +4 -77
- package/srpc/common-rpc_test.go +81 -0
- package/srpc/errors.go +26 -1
- package/srpc/handler.ts +1 -2
- package/srpc/index.ts +0 -4
- package/srpc/invoker-prefix.go +1 -1
- package/srpc/invoker.go +2 -2
- package/srpc/invoker.ts +5 -9
- package/srpc/message.go +1 -1
- package/srpc/msg-stream.go +1 -9
- package/srpc/mux-verbose.go +1 -1
- package/srpc/mux.go +1 -1
- package/srpc/rpcproto.pb.cc +4 -15
- package/srpc/rpcproto.pb.go +102 -419
- package/srpc/rpcproto.pb.h +0 -59
- package/srpc/rpcproto.pb.rs +0 -45
- package/srpc/rpcproto.pb.ts +0 -62
- package/srpc/rpcproto.proto +0 -16
- package/srpc/server-rpc.go +1 -1
- package/srpc/server-rpc.ts +1 -6
- package/srpc/server.test.ts +49 -1
- package/srpc/stream-pipe.go +1 -1
- package/srpc/stream-rwc.go +1 -1
- package/srpc/stream.go +0 -20
- package/dist/rpcstream/receipt.test.d.ts +0 -1
- package/dist/rpcstream/receipt.test.js +0 -41
- package/dist/srpc/call-receipt.d.ts +0 -17
- package/dist/srpc/call-receipt.js +0 -106
- package/dist/srpc/call-receipt.test.d.ts +0 -1
- package/dist/srpc/call-receipt.test.js +0 -375
- package/dist/srpc/server-invocation.d.ts +0 -17
- package/dist/srpc/server-invocation.js +0 -37
- package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
- package/srpc/call-receipt-e2e_test.go +0 -111
- package/srpc/call-receipt.go +0 -112
- package/srpc/call-receipt.test.ts +0 -441
- package/srpc/call-receipt.ts +0 -131
- package/srpc/call-receipt_test.go +0 -536
- package/srpc/schema-ownership_test.go +0 -115
- package/srpc/server-invocation.go +0 -40
- package/srpc/server-invocation.ts +0 -76
package/dist/srpc/server.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, beforeEach, expect, vi } from 'vitest';
|
|
2
2
|
import { pipe } from 'it-pipe';
|
|
3
3
|
import { createHandler, createMux, Server, Client, StreamConn, ChannelStream, combineUint8ArrayListTransform, Packet, } from '../srpc/index.js';
|
|
4
|
-
import { EchoerDefinition, EchoerServer, EchoerServiceName, runClientTest, } from '../echo/index.js';
|
|
4
|
+
import { EchoerDefinition, EchoerServer, EchoerServiceName, EchoMsg, runClientTest, } from '../echo/index.js';
|
|
5
5
|
import { runAbortControllerTest, runRpcStreamTest, } from '../echo/client-test.js';
|
|
6
6
|
describe('srpc server', () => {
|
|
7
7
|
let client;
|
|
@@ -39,6 +39,33 @@ describe('srpc server', () => {
|
|
|
39
39
|
it('should pass rpc stream tests', async () => {
|
|
40
40
|
await runRpcStreamTest(client);
|
|
41
41
|
});
|
|
42
|
+
it('passes the exact invocation signal after async request decode', async () => {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
let observedSignal;
|
|
45
|
+
const handler = createHandler(EchoerDefinition, {
|
|
46
|
+
Echo: async (request, signal) => {
|
|
47
|
+
observedSignal = signal;
|
|
48
|
+
return request;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const invokeFn = await handler.lookupMethod(EchoerServiceName, 'Echo');
|
|
52
|
+
if (!invokeFn) {
|
|
53
|
+
throw new Error('Echo method was not found');
|
|
54
|
+
}
|
|
55
|
+
const request = EchoMsg.create({ body: 'signal identity' });
|
|
56
|
+
const drained = Promise.withResolvers();
|
|
57
|
+
await invokeFn((async function* () {
|
|
58
|
+
await Promise.resolve();
|
|
59
|
+
yield EchoMsg.toBinary(request);
|
|
60
|
+
})(), async (source) => {
|
|
61
|
+
for await (const _data of source) {
|
|
62
|
+
// Drain the encoded response so the invocation pipeline completes.
|
|
63
|
+
}
|
|
64
|
+
drained.resolve();
|
|
65
|
+
}, controller.signal);
|
|
66
|
+
await drained.promise;
|
|
67
|
+
expect(observedSignal).toBe(controller.signal);
|
|
68
|
+
});
|
|
42
69
|
it('keeps detached server-streaming responses open after request source completes', async () => {
|
|
43
70
|
const mux = createMux();
|
|
44
71
|
const response = new TextEncoder().encode('delayed init');
|
|
@@ -166,6 +193,13 @@ describe('srpc server', () => {
|
|
|
166
193
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
167
194
|
expect(removeEventListener).toHaveBeenCalledWith('abort', expect.any(Function));
|
|
168
195
|
});
|
|
196
|
+
it('settles a late abort after the call has already closed', async () => {
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
vi.spyOn(controller.signal, 'removeEventListener').mockImplementation(() => undefined);
|
|
199
|
+
await client.request(EchoerServiceName, 'Echo', new TextEncoder().encode('late abort'), controller.signal);
|
|
200
|
+
controller.abort();
|
|
201
|
+
await Promise.resolve();
|
|
202
|
+
});
|
|
169
203
|
it('tears down passive channel close state', async () => {
|
|
170
204
|
const { port1, port2 } = new MessageChannel();
|
|
171
205
|
const opts = { idleTimeoutMs: 1000, keepAliveMs: 1000 };
|
package/echo/echo.go
CHANGED
|
@@ -7,15 +7,15 @@ import (
|
|
|
7
7
|
|
|
8
8
|
// _ is a type assertion
|
|
9
9
|
var (
|
|
10
|
-
_ srpc.StreamRecv[*EchoMsg] =
|
|
11
|
-
_ srpc.StreamRecv[*EchoMsg] =
|
|
10
|
+
_ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
|
|
11
|
+
_ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoServerStreamClient(nil)
|
|
12
12
|
|
|
13
|
-
_ srpc.StreamSend[*EchoMsg] =
|
|
14
|
-
_ srpc.StreamSend[*EchoMsg] =
|
|
13
|
+
_ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
|
|
14
|
+
_ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoClientStreamClient(nil)
|
|
15
15
|
|
|
16
|
-
_ srpc.StreamSendAndClose[*EchoMsg] =
|
|
17
|
-
_ srpc.StreamSendAndClose[*EchoMsg] =
|
|
16
|
+
_ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoBidiStreamStream(nil)
|
|
17
|
+
_ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoServerStreamStream(nil)
|
|
18
18
|
|
|
19
|
-
_ srpc.StreamRecv[*rpcstream.RpcStreamPacket] =
|
|
20
|
-
_ srpc.StreamSendAndClose[*rpcstream.RpcStreamPacket] =
|
|
19
|
+
_ srpc.StreamRecv[*rpcstream.RpcStreamPacket] = SRPCEchoer_RpcStreamStream(nil)
|
|
20
|
+
_ srpc.StreamSendAndClose[*rpcstream.RpcStreamPacket] = SRPCEchoer_RpcStreamStream(nil)
|
|
21
21
|
)
|
package/echo/echo.pb.go
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code generated by protoc-gen-go-lite. DO NOT EDIT.
|
|
2
|
-
// protoc-gen-go-lite version: v0.
|
|
2
|
+
// protoc-gen-go-lite version: v0.16.0
|
|
3
3
|
// source: github.com/aperturerobotics/starpc/echo/echo.proto
|
|
4
4
|
|
|
5
5
|
package echo
|
|
@@ -8,8 +8,6 @@ import (
|
|
|
8
8
|
fmt "fmt"
|
|
9
9
|
io "io"
|
|
10
10
|
slices "slices"
|
|
11
|
-
strconv "strconv"
|
|
12
|
-
strings "strings"
|
|
13
11
|
|
|
14
12
|
protobuf_go_lite "github.com/aperturerobotics/protobuf-go-lite"
|
|
15
13
|
json "github.com/aperturerobotics/protobuf-go-lite/json"
|
|
@@ -141,13 +139,10 @@ func (m *EchoMsg) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
|
|
141
139
|
var l int
|
|
142
140
|
_ = l
|
|
143
141
|
if m.unknownFields != nil {
|
|
144
|
-
i
|
|
145
|
-
copy(dAtA[i:], m.unknownFields)
|
|
142
|
+
i = protobuf_go_lite.EncodeRawBytes(dAtA, i, m.unknownFields)
|
|
146
143
|
}
|
|
147
144
|
if len(m.Body) > 0 {
|
|
148
|
-
i
|
|
149
|
-
copy(dAtA[i:], m.Body)
|
|
150
|
-
i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(m.Body)))
|
|
145
|
+
i = protobuf_go_lite.EncodeString(dAtA, i, m.Body)
|
|
151
146
|
i--
|
|
152
147
|
dAtA[i] = 0xa
|
|
153
148
|
}
|
|
@@ -160,26 +155,19 @@ func (m *EchoMsg) SizeVT() (n int) {
|
|
|
160
155
|
}
|
|
161
156
|
var l int
|
|
162
157
|
_ = l
|
|
163
|
-
|
|
164
|
-
if l > 0 {
|
|
165
|
-
n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l))
|
|
166
|
-
}
|
|
158
|
+
n += protobuf_go_lite.SizeStringNonEmpty(1, m.Body)
|
|
167
159
|
n += len(m.unknownFields)
|
|
168
160
|
return n
|
|
169
161
|
}
|
|
170
162
|
|
|
171
163
|
func (x *EchoMsg) MarshalProtoText() string {
|
|
172
|
-
var sb
|
|
173
|
-
|
|
164
|
+
var sb protobuf_go_lite.TextBuilder
|
|
165
|
+
initialLen := protobuf_go_lite.TextStartMessage(&sb, "EchoMsg")
|
|
174
166
|
if x.Body != "" {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
sb.WriteString("body: ")
|
|
179
|
-
sb.WriteString(strconv.Quote(x.Body))
|
|
167
|
+
protobuf_go_lite.TextWriteFieldPrefix(&sb, initialLen, "body")
|
|
168
|
+
protobuf_go_lite.TextWriteString(&sb, x.Body)
|
|
180
169
|
}
|
|
181
|
-
|
|
182
|
-
return sb.String()
|
|
170
|
+
return protobuf_go_lite.TextFinishMessage(&sb)
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
func (x *EchoMsg) String() string {
|
|
@@ -210,24 +198,12 @@ func (m *EchoMsg) UnmarshalVT(dAtA []byte) error {
|
|
|
210
198
|
if wireType != 2 {
|
|
211
199
|
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
|
|
212
200
|
}
|
|
213
|
-
var
|
|
214
|
-
|
|
201
|
+
var v string
|
|
202
|
+
v, iNdEx, err = protobuf_go_lite.DecodeString(dAtA, iNdEx)
|
|
215
203
|
if err != nil {
|
|
216
204
|
return err
|
|
217
205
|
}
|
|
218
|
-
|
|
219
|
-
if intStringLen < 0 {
|
|
220
|
-
return protobuf_go_lite.ErrInvalidLength
|
|
221
|
-
}
|
|
222
|
-
postIndex := iNdEx + intStringLen
|
|
223
|
-
if postIndex < 0 {
|
|
224
|
-
return protobuf_go_lite.ErrInvalidLength
|
|
225
|
-
}
|
|
226
|
-
if postIndex > l {
|
|
227
|
-
return io.ErrUnexpectedEOF
|
|
228
|
-
}
|
|
229
|
-
m.Body = string(dAtA[iNdEx:postIndex])
|
|
230
|
-
iNdEx = postIndex
|
|
206
|
+
m.Body = v
|
|
231
207
|
default:
|
|
232
208
|
iNdEx = preIndex
|
|
233
209
|
skippy, err := protobuf_go_lite.Skip(dAtA[iNdEx:])
|
package/echo/echo_srpc.pb.cpp
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//go:build deps_only
|
|
2
2
|
|
|
3
3
|
// Code generated by protoc-gen-starpc-cpp. DO NOT EDIT.
|
|
4
|
-
// protoc-gen-starpc-cpp version: v0.49.
|
|
4
|
+
// protoc-gen-starpc-cpp version: v0.49.21
|
|
5
5
|
// source: github.com/aperturerobotics/starpc/echo/echo.proto
|
|
6
6
|
|
|
7
7
|
#include "echo_srpc.pb.hpp"
|
package/echo/echo_srpc.pb.go
CHANGED
package/echo/echo_srpc.pb.hpp
CHANGED
package/echo/echo_srpc.pb.rs
CHANGED
package/echo/server.go
CHANGED
package/go.mod
CHANGED
|
@@ -3,7 +3,7 @@ module github.com/aperturerobotics/starpc
|
|
|
3
3
|
go 1.25.0
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
-
github.com/aperturerobotics/common v0.
|
|
6
|
+
github.com/aperturerobotics/common v0.35.0 // latest
|
|
7
7
|
github.com/aperturerobotics/protobuf-go-lite v0.16.0 // latest
|
|
8
8
|
github.com/aperturerobotics/util v1.34.9 // latest
|
|
9
9
|
)
|
package/go.sum
CHANGED
|
@@ -4,6 +4,8 @@ github.com/aperturerobotics/cli v1.1.0 h1:7a+YRC+EY3npAnTzhHV5gLCiw91KS0Ts3XwLIL
|
|
|
4
4
|
github.com/aperturerobotics/cli v1.1.0/go.mod h1:M7BFP9wow5ytTzMyJQOOO991fGfsUqdTI7gGEsHfTQ8=
|
|
5
5
|
github.com/aperturerobotics/common v0.34.4 h1:RQTOm6LxEnucXkED/kruhKKAOL6tbaPzMgfHPKaES4k=
|
|
6
6
|
github.com/aperturerobotics/common v0.34.4/go.mod h1:xnb1VBRs3x2o1PaKg0OlP7SqDaC9Hmrmt9xH85rOS+A=
|
|
7
|
+
github.com/aperturerobotics/common v0.35.0 h1:VU4OPMXJyqT37lTOtNEMa3QLW5tR8YIBAKbjgxSzuZs=
|
|
8
|
+
github.com/aperturerobotics/common v0.35.0/go.mod h1:xnb1VBRs3x2o1PaKg0OlP7SqDaC9Hmrmt9xH85rOS+A=
|
|
7
9
|
github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260705010911-9f53feac967b h1:MMk+AbWPCMfqc2Lm2bJl8lCCsuvlM2+C0CP10fpE3bc=
|
|
8
10
|
github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260705010911-9f53feac967b/go.mod h1:OBb/beWmr/pDIZAUfi86j/4tBh2v5ctTxKMqSnh9c/4=
|
|
9
11
|
github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260712054757-d8078c296c17 h1:WT1YmZjJf8fUF1zKbeNNKXFA+t8Wtbr8AVMAmdKcrUU=
|
|
@@ -2,7 +2,6 @@ package main
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
-
"errors"
|
|
6
5
|
"fmt"
|
|
7
6
|
"io"
|
|
8
7
|
"net"
|
|
@@ -15,32 +14,13 @@ import (
|
|
|
15
14
|
const bodyTxt = "hello world via starpc cross-language e2e test"
|
|
16
15
|
|
|
17
16
|
func main() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
addrIndex := 1
|
|
21
|
-
if receiptMode {
|
|
22
|
-
if len(os.Args) < 3 {
|
|
23
|
-
fmt.Fprintf(os.Stderr, "usage: go-client receipt <case> <addr>\n")
|
|
24
|
-
os.Exit(1)
|
|
25
|
-
}
|
|
26
|
-
receiptCase = os.Args[2]
|
|
27
|
-
addrIndex = 3
|
|
28
|
-
switch receiptCase {
|
|
29
|
-
case "commit", "abort", "loss", "bare-close":
|
|
30
|
-
default:
|
|
31
|
-
fmt.Fprintf(os.Stderr, "unknown receipt case: %s\n", receiptCase)
|
|
32
|
-
os.Exit(1)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if len(os.Args) <= addrIndex {
|
|
36
|
-
fmt.Fprintf(os.Stderr, "usage: go-client [receipt <case>] <addr>\n")
|
|
17
|
+
if len(os.Args) <= 1 {
|
|
18
|
+
fmt.Fprintf(os.Stderr, "usage: go-client <addr>\n")
|
|
37
19
|
os.Exit(1)
|
|
38
20
|
}
|
|
39
|
-
addr := os.Args[
|
|
40
|
-
var conn net.Conn
|
|
21
|
+
addr := os.Args[1]
|
|
41
22
|
openStream := func(ctx context.Context, msgHandler srpc.PacketDataHandler, closeHandler srpc.CloseHandler) (srpc.PacketWriter, error) {
|
|
42
|
-
|
|
43
|
-
conn, err = net.Dial("tcp", addr) //nolint:gosec
|
|
23
|
+
conn, err := net.Dial("tcp", addr) //nolint:gosec
|
|
44
24
|
if err != nil {
|
|
45
25
|
return nil, err
|
|
46
26
|
}
|
|
@@ -52,35 +32,22 @@ func main() {
|
|
|
52
32
|
client := srpc.NewClient(openStream)
|
|
53
33
|
echoClient := echo.NewSRPCEchoerClient(client)
|
|
54
34
|
ctx := context.Background()
|
|
55
|
-
if receiptMode {
|
|
56
|
-
if err := testReceipt(ctx, client, &conn, receiptCase); err != nil {
|
|
57
|
-
fmt.Fprintf(os.Stderr, "receipt test failed: %v\n", err)
|
|
58
|
-
os.Exit(1)
|
|
59
|
-
}
|
|
60
|
-
fmt.Println("All tests passed.")
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
35
|
if err := testUnary(ctx, echoClient); err != nil {
|
|
65
36
|
fmt.Fprintf(os.Stderr, "unary test failed: %v\n", err)
|
|
66
37
|
os.Exit(1)
|
|
67
38
|
}
|
|
68
|
-
|
|
69
39
|
if err := testServerStream(ctx, echoClient); err != nil {
|
|
70
40
|
fmt.Fprintf(os.Stderr, "server stream test failed: %v\n", err)
|
|
71
41
|
os.Exit(1)
|
|
72
42
|
}
|
|
73
|
-
|
|
74
43
|
if err := testClientStream(ctx, echoClient); err != nil {
|
|
75
44
|
fmt.Fprintf(os.Stderr, "client stream test failed: %v\n", err)
|
|
76
45
|
os.Exit(1)
|
|
77
46
|
}
|
|
78
|
-
|
|
79
47
|
if err := testBidiStream(ctx, echoClient); err != nil {
|
|
80
48
|
fmt.Fprintf(os.Stderr, "bidi stream test failed: %v\n", err)
|
|
81
49
|
os.Exit(1)
|
|
82
50
|
}
|
|
83
|
-
|
|
84
51
|
fmt.Println("All tests passed.")
|
|
85
52
|
}
|
|
86
53
|
|
|
@@ -97,79 +64,6 @@ func testUnary(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
|
97
64
|
return nil
|
|
98
65
|
}
|
|
99
66
|
|
|
100
|
-
func testReceipt(
|
|
101
|
-
ctx context.Context,
|
|
102
|
-
client srpc.Client,
|
|
103
|
-
conn *net.Conn,
|
|
104
|
-
receiptCase string,
|
|
105
|
-
) error {
|
|
106
|
-
fmt.Printf("Testing held unary receipt (%s)...\n", receiptCase)
|
|
107
|
-
out := new(echo.EchoMsg)
|
|
108
|
-
receipt, err := srpc.ExecCallReceipt(
|
|
109
|
-
ctx, client, echo.SRPCEchoerServiceID, "Echo",
|
|
110
|
-
&echo.EchoMsg{Body: bodyTxt}, out,
|
|
111
|
-
)
|
|
112
|
-
if err != nil {
|
|
113
|
-
return fmt.Errorf("receipt call: %w", err)
|
|
114
|
-
}
|
|
115
|
-
if out.GetBody() != bodyTxt {
|
|
116
|
-
return fmt.Errorf("expected %q got %q", bodyTxt, out.GetBody())
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
switch receiptCase {
|
|
120
|
-
case "commit":
|
|
121
|
-
if err := receipt.Commit(); err != nil {
|
|
122
|
-
return fmt.Errorf("receipt commit: %w", err)
|
|
123
|
-
}
|
|
124
|
-
case "abort":
|
|
125
|
-
if err := receipt.Abort(); err != nil {
|
|
126
|
-
return fmt.Errorf("receipt abort: %w", err)
|
|
127
|
-
}
|
|
128
|
-
case "loss":
|
|
129
|
-
if *conn == nil {
|
|
130
|
-
return errors.New("receipt connection is nil")
|
|
131
|
-
}
|
|
132
|
-
tcpConn, ok := (*conn).(*net.TCPConn)
|
|
133
|
-
if !ok {
|
|
134
|
-
return errors.New("receipt connection is not TCP")
|
|
135
|
-
}
|
|
136
|
-
if err := tcpConn.SetLinger(0); err != nil {
|
|
137
|
-
return fmt.Errorf("set receipt loss linger: %w", err)
|
|
138
|
-
}
|
|
139
|
-
if err := tcpConn.Close(); err != nil {
|
|
140
|
-
return fmt.Errorf("close lost connection: %w", err)
|
|
141
|
-
}
|
|
142
|
-
if err := receipt.Commit(); err == nil {
|
|
143
|
-
return errors.New("loss receipt commit unexpectedly succeeded")
|
|
144
|
-
}
|
|
145
|
-
case "bare-close":
|
|
146
|
-
if *conn == nil {
|
|
147
|
-
return errors.New("receipt connection is nil")
|
|
148
|
-
}
|
|
149
|
-
tcpConn, ok := (*conn).(*net.TCPConn)
|
|
150
|
-
if !ok {
|
|
151
|
-
return errors.New("receipt connection is not TCP")
|
|
152
|
-
}
|
|
153
|
-
if err := tcpConn.CloseWrite(); err != nil {
|
|
154
|
-
return fmt.Errorf("close receipt write side: %w", err)
|
|
155
|
-
}
|
|
156
|
-
if err := receipt.Commit(); err == nil {
|
|
157
|
-
return errors.New("bare-close receipt commit unexpectedly succeeded")
|
|
158
|
-
}
|
|
159
|
-
default:
|
|
160
|
-
return fmt.Errorf("unknown receipt case: %s", receiptCase)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if err := emitReceiptEvent(
|
|
164
|
-
fmt.Sprintf("CLIENT_RECEIPT_RESOLVED %s", receiptTerminalName(receiptCase)),
|
|
165
|
-
); err != nil {
|
|
166
|
-
return fmt.Errorf("record receipt resolution: %w", err)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
fmt.Println(" PASSED")
|
|
170
|
-
return nil
|
|
171
|
-
}
|
|
172
|
-
|
|
173
67
|
func testServerStream(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
174
68
|
fmt.Println("Testing ServerStream RPC...")
|
|
175
69
|
strm, err := client.EchoServerStream(ctx, &echo.EchoMsg{Body: bodyTxt})
|
|
@@ -249,35 +143,3 @@ func testBidiStream(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
|
249
143
|
fmt.Println(" PASSED")
|
|
250
144
|
return nil
|
|
251
145
|
}
|
|
252
|
-
|
|
253
|
-
func receiptTerminalName(receiptCase string) string {
|
|
254
|
-
switch receiptCase {
|
|
255
|
-
case "commit":
|
|
256
|
-
return "committed"
|
|
257
|
-
case "abort":
|
|
258
|
-
return "canceled"
|
|
259
|
-
case "loss":
|
|
260
|
-
return "transportLost"
|
|
261
|
-
case "bare-close":
|
|
262
|
-
return "closed"
|
|
263
|
-
default:
|
|
264
|
-
return "unknown"
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
func emitReceiptEvent(line string) error {
|
|
269
|
-
fmt.Println(line)
|
|
270
|
-
fifo := os.Getenv("RECEIPT_EVENT_FIFO")
|
|
271
|
-
if fifo == "" {
|
|
272
|
-
return nil
|
|
273
|
-
}
|
|
274
|
-
file, err := os.OpenFile(fifo, os.O_WRONLY, 0) //nolint:gosec // FIFO path is created by the local integration runner.
|
|
275
|
-
if err != nil {
|
|
276
|
-
return err
|
|
277
|
-
}
|
|
278
|
-
if _, err := file.WriteString(line + "\n"); err != nil {
|
|
279
|
-
_ = file.Close()
|
|
280
|
-
return err
|
|
281
|
-
}
|
|
282
|
-
return file.Close()
|
|
283
|
-
}
|
|
@@ -2,13 +2,10 @@ package main
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
-
"encoding/binary"
|
|
6
5
|
"fmt"
|
|
7
6
|
"net"
|
|
8
7
|
"os"
|
|
9
8
|
"os/signal"
|
|
10
|
-
"sync"
|
|
11
|
-
"sync/atomic"
|
|
12
9
|
|
|
13
10
|
"github.com/aperturerobotics/starpc/echo"
|
|
14
11
|
"github.com/aperturerobotics/starpc/srpc"
|
|
@@ -18,74 +15,13 @@ func main() {
|
|
|
18
15
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
19
16
|
defer cancel()
|
|
20
17
|
|
|
21
|
-
receiptMode := len(os.Args) > 1 && os.Args[1] == "receipt"
|
|
22
|
-
receiptCase := ""
|
|
23
|
-
if receiptMode {
|
|
24
|
-
if len(os.Args) < 3 {
|
|
25
|
-
fmt.Fprintln(os.Stderr, "usage: go-server receipt <case>")
|
|
26
|
-
os.Exit(1)
|
|
27
|
-
}
|
|
28
|
-
receiptCase = os.Args[2]
|
|
29
|
-
switch receiptCase {
|
|
30
|
-
case "commit", "abort", "loss", "bare-close":
|
|
31
|
-
default:
|
|
32
|
-
fmt.Fprintf(os.Stderr, "unknown receipt case: %s\n", receiptCase)
|
|
33
|
-
os.Exit(1)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
18
|
mux := srpc.NewMux()
|
|
37
19
|
echoServer := echo.NewEchoServer(mux)
|
|
38
20
|
if err := echo.SRPCRegisterEchoer(mux, echoServer); err != nil {
|
|
39
21
|
fmt.Fprintf(os.Stderr, "register error: %v\n", err)
|
|
40
22
|
os.Exit(1)
|
|
41
23
|
}
|
|
42
|
-
|
|
43
|
-
var finishReceipt func()
|
|
44
|
-
var receiptCommitted atomic.Bool
|
|
45
|
-
if receiptMode {
|
|
46
|
-
done := make(chan struct{})
|
|
47
|
-
var doneOnce sync.Once
|
|
48
|
-
receiptDone = done
|
|
49
|
-
finishReceipt = func() {
|
|
50
|
-
doneOnce.Do(func() {
|
|
51
|
-
close(done)
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
var invoker srpc.Invoker = mux
|
|
56
|
-
if receiptMode {
|
|
57
|
-
invoker = srpc.InvokerFunc(func(
|
|
58
|
-
serviceID, methodID string,
|
|
59
|
-
strm srpc.Stream,
|
|
60
|
-
) (bool, error) {
|
|
61
|
-
handled, err := mux.InvokeMethod(serviceID, methodID, strm)
|
|
62
|
-
if err != nil || !handled {
|
|
63
|
-
return handled, err
|
|
64
|
-
}
|
|
65
|
-
invocation, ok := srpc.GetServerInvocation(strm.Context())
|
|
66
|
-
if !ok {
|
|
67
|
-
return true, context.Canceled
|
|
68
|
-
}
|
|
69
|
-
kind, waitErr := invocation.WaitTerminal(context.Background())
|
|
70
|
-
markerErr := emitReceiptEvent(
|
|
71
|
-
fmt.Sprintf("SERVER_RECEIPT_TERMINAL %s", terminalName(kind)),
|
|
72
|
-
)
|
|
73
|
-
if waitErr != nil {
|
|
74
|
-
return true, waitErr
|
|
75
|
-
}
|
|
76
|
-
if markerErr != nil {
|
|
77
|
-
return true, markerErr
|
|
78
|
-
}
|
|
79
|
-
if kind == srpc.TerminalKind_TERMINAL_KIND_COMMITTED {
|
|
80
|
-
receiptCommitted.Store(true)
|
|
81
|
-
}
|
|
82
|
-
if kind != srpc.TerminalKind_TERMINAL_KIND_COMMITTED {
|
|
83
|
-
finishReceipt()
|
|
84
|
-
}
|
|
85
|
-
return true, nil
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
server := srpc.NewServer(invoker)
|
|
24
|
+
server := srpc.NewServer(mux)
|
|
89
25
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
90
26
|
if err != nil {
|
|
91
27
|
fmt.Fprintf(os.Stderr, "listen error: %v\n", err)
|
|
@@ -93,120 +29,16 @@ func main() {
|
|
|
93
29
|
}
|
|
94
30
|
defer ln.Close()
|
|
95
31
|
|
|
96
|
-
if receiptMode {
|
|
97
|
-
go func() {
|
|
98
|
-
<-receiptDone
|
|
99
|
-
_ = ln.Close()
|
|
100
|
-
}()
|
|
101
|
-
}
|
|
102
|
-
|
|
103
32
|
fmt.Printf("LISTENING %s\n", ln.Addr().String())
|
|
104
|
-
|
|
105
33
|
go func() {
|
|
106
34
|
<-ctx.Done()
|
|
107
35
|
_ = ln.Close()
|
|
108
36
|
}()
|
|
109
|
-
|
|
110
37
|
for {
|
|
111
38
|
conn, err := ln.Accept()
|
|
112
39
|
if err != nil {
|
|
113
40
|
return
|
|
114
41
|
}
|
|
115
|
-
|
|
116
|
-
stream = conn
|
|
117
|
-
if receiptMode {
|
|
118
|
-
stream = &receiptConn{
|
|
119
|
-
Conn: conn,
|
|
120
|
-
finishReceipt: finishReceipt,
|
|
121
|
-
committed: &receiptCommitted,
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
go server.HandleStream(ctx, stream)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
type receiptConn struct {
|
|
129
|
-
net.Conn
|
|
130
|
-
finishReceipt func()
|
|
131
|
-
committed *atomic.Bool
|
|
132
|
-
inspect []byte
|
|
133
|
-
ackPending bool
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
func (c *receiptConn) Write(p []byte) (int, error) {
|
|
137
|
-
if !c.ackPending {
|
|
138
|
-
if err := c.observeReceiptPackets(p); err != nil {
|
|
139
|
-
return 0, err
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
n, err := c.Conn.Write(p)
|
|
143
|
-
if err != nil {
|
|
144
|
-
return n, err
|
|
145
|
-
}
|
|
146
|
-
if c.ackPending && n == len(p) {
|
|
147
|
-
c.finishReceipt()
|
|
148
|
-
c.ackPending = false
|
|
149
|
-
}
|
|
150
|
-
return n, nil
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
func (c *receiptConn) observeReceiptPackets(p []byte) error {
|
|
154
|
-
c.inspect = append(c.inspect, p...)
|
|
155
|
-
for len(c.inspect) >= 4 {
|
|
156
|
-
size := int(binary.LittleEndian.Uint32(c.inspect[:4]))
|
|
157
|
-
if len(c.inspect) < 4+size {
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
pkt := &srpc.Packet{}
|
|
161
|
-
if err := pkt.UnmarshalVT(c.inspect[4 : 4+size]); err != nil {
|
|
162
|
-
return err
|
|
163
|
-
}
|
|
164
|
-
c.inspect = c.inspect[4+size:]
|
|
165
|
-
data := pkt.GetCallData()
|
|
166
|
-
if !c.committed.Load() {
|
|
167
|
-
continue
|
|
168
|
-
}
|
|
169
|
-
if data == nil || !data.GetComplete() || data.GetError() != "" {
|
|
170
|
-
continue
|
|
171
|
-
}
|
|
172
|
-
if err := emitReceiptEvent("SERVER_RECEIPT_ACK_WRITE committed"); err != nil {
|
|
173
|
-
return err
|
|
174
|
-
}
|
|
175
|
-
c.ackPending = true
|
|
176
|
-
}
|
|
177
|
-
return nil
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
func terminalName(kind srpc.TerminalKind) string {
|
|
181
|
-
switch kind {
|
|
182
|
-
case srpc.TerminalKind_TERMINAL_KIND_COMMITTED:
|
|
183
|
-
return "committed"
|
|
184
|
-
case srpc.TerminalKind_TERMINAL_KIND_CANCELED:
|
|
185
|
-
return "canceled"
|
|
186
|
-
case srpc.TerminalKind_TERMINAL_KIND_TRANSPORT_LOST:
|
|
187
|
-
return "transportLost"
|
|
188
|
-
case srpc.TerminalKind_TERMINAL_KIND_CLOSED:
|
|
189
|
-
return "closed"
|
|
190
|
-
case srpc.TerminalKind_TERMINAL_KIND_ABANDONED:
|
|
191
|
-
return "abandoned"
|
|
192
|
-
default:
|
|
193
|
-
return "unknown"
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
func emitReceiptEvent(line string) error {
|
|
198
|
-
fmt.Println(line)
|
|
199
|
-
fifo := os.Getenv("RECEIPT_EVENT_FIFO")
|
|
200
|
-
if fifo == "" {
|
|
201
|
-
return nil
|
|
202
|
-
}
|
|
203
|
-
file, err := os.OpenFile(fifo, os.O_WRONLY, 0) //nolint:gosec // FIFO path is created by the local integration runner.
|
|
204
|
-
if err != nil {
|
|
205
|
-
return err
|
|
206
|
-
}
|
|
207
|
-
if _, err := file.WriteString(line + "\n"); err != nil {
|
|
208
|
-
_ = file.Close()
|
|
209
|
-
return err
|
|
42
|
+
go server.HandleStream(ctx, conn)
|
|
210
43
|
}
|
|
211
|
-
return file.Close()
|
|
212
44
|
}
|