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.
Files changed (81) hide show
  1. package/dist/integration/cross-language/ts-client.js +5 -79
  2. package/dist/integration/cross-language/ts-server.js +4 -100
  3. package/dist/srpc/client.d.ts +1 -3
  4. package/dist/srpc/client.js +2 -22
  5. package/dist/srpc/common-rpc.d.ts +1 -9
  6. package/dist/srpc/common-rpc.js +4 -62
  7. package/dist/srpc/handler.d.ts +1 -2
  8. package/dist/srpc/index.d.ts +0 -4
  9. package/dist/srpc/index.js +0 -3
  10. package/dist/srpc/invoker.d.ts +1 -2
  11. package/dist/srpc/rpcproto.pb.d.ts +0 -44
  12. package/dist/srpc/rpcproto.pb.js +0 -53
  13. package/dist/srpc/server-rpc.js +1 -3
  14. package/dist/srpc/server.test.js +35 -1
  15. package/echo/echo.go +8 -8
  16. package/echo/echo.pb.go +12 -36
  17. package/echo/echo_srpc.pb.cpp +1 -1
  18. package/echo/echo_srpc.pb.go +1 -1
  19. package/echo/echo_srpc.pb.hpp +1 -1
  20. package/echo/echo_srpc.pb.rs +1 -1
  21. package/echo/server.go +1 -1
  22. package/go.mod +1 -1
  23. package/go.sum +2 -0
  24. package/integration/cross-language/go-client/main.go +4 -142
  25. package/integration/cross-language/go-server/main.go +2 -170
  26. package/integration/cross-language/run.bash +5 -105
  27. package/integration/cross-language/ts-client.ts +5 -90
  28. package/integration/cross-language/ts-server.ts +4 -112
  29. package/mock/mock.go +1 -1
  30. package/mock/mock.pb.go +12 -36
  31. package/mock/mock_srpc.pb.cpp +1 -1
  32. package/mock/mock_srpc.pb.go +1 -1
  33. package/mock/mock_srpc.pb.hpp +1 -1
  34. package/mock/mock_srpc.pb.rs +1 -1
  35. package/package.json +1 -1
  36. package/srpc/client-prefix.go +1 -1
  37. package/srpc/client-set.go +1 -1
  38. package/srpc/client.go +1 -1
  39. package/srpc/client.ts +4 -31
  40. package/srpc/common-rpc.go +15 -69
  41. package/srpc/common-rpc.ts +4 -77
  42. package/srpc/common-rpc_test.go +81 -0
  43. package/srpc/errors.go +26 -1
  44. package/srpc/handler.ts +1 -2
  45. package/srpc/index.ts +0 -4
  46. package/srpc/invoker-prefix.go +1 -1
  47. package/srpc/invoker.go +2 -2
  48. package/srpc/invoker.ts +5 -9
  49. package/srpc/message.go +1 -1
  50. package/srpc/msg-stream.go +1 -9
  51. package/srpc/mux-verbose.go +1 -1
  52. package/srpc/mux.go +1 -1
  53. package/srpc/rpcproto.pb.cc +4 -15
  54. package/srpc/rpcproto.pb.go +102 -419
  55. package/srpc/rpcproto.pb.h +0 -59
  56. package/srpc/rpcproto.pb.rs +0 -45
  57. package/srpc/rpcproto.pb.ts +0 -62
  58. package/srpc/rpcproto.proto +0 -16
  59. package/srpc/server-rpc.go +1 -1
  60. package/srpc/server-rpc.ts +1 -6
  61. package/srpc/server.test.ts +49 -1
  62. package/srpc/stream-pipe.go +1 -1
  63. package/srpc/stream-rwc.go +1 -1
  64. package/srpc/stream.go +0 -20
  65. package/dist/rpcstream/receipt.test.d.ts +0 -1
  66. package/dist/rpcstream/receipt.test.js +0 -41
  67. package/dist/srpc/call-receipt.d.ts +0 -17
  68. package/dist/srpc/call-receipt.js +0 -106
  69. package/dist/srpc/call-receipt.test.d.ts +0 -1
  70. package/dist/srpc/call-receipt.test.js +0 -375
  71. package/dist/srpc/server-invocation.d.ts +0 -17
  72. package/dist/srpc/server-invocation.js +0 -37
  73. package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
  74. package/srpc/call-receipt-e2e_test.go +0 -111
  75. package/srpc/call-receipt.go +0 -112
  76. package/srpc/call-receipt.test.ts +0 -441
  77. package/srpc/call-receipt.ts +0 -131
  78. package/srpc/call-receipt_test.go +0 -536
  79. package/srpc/schema-ownership_test.go +0 -115
  80. package/srpc/server-invocation.go +0 -40
  81. package/srpc/server-invocation.ts +0 -76
@@ -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] = (SRPCEchoer_EchoBidiStreamClient)(nil)
11
- _ srpc.StreamRecv[*EchoMsg] = (SRPCEchoer_EchoServerStreamClient)(nil)
10
+ _ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
11
+ _ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoServerStreamClient(nil)
12
12
 
13
- _ srpc.StreamSend[*EchoMsg] = (SRPCEchoer_EchoBidiStreamClient)(nil)
14
- _ srpc.StreamSend[*EchoMsg] = (SRPCEchoer_EchoClientStreamClient)(nil)
13
+ _ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
14
+ _ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoClientStreamClient(nil)
15
15
 
16
- _ srpc.StreamSendAndClose[*EchoMsg] = (SRPCEchoer_EchoBidiStreamStream)(nil)
17
- _ srpc.StreamSendAndClose[*EchoMsg] = (SRPCEchoer_EchoServerStreamStream)(nil)
16
+ _ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoBidiStreamStream(nil)
17
+ _ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoServerStreamStream(nil)
18
18
 
19
- _ srpc.StreamRecv[*rpcstream.RpcStreamPacket] = (SRPCEchoer_RpcStreamStream)(nil)
20
- _ srpc.StreamSendAndClose[*rpcstream.RpcStreamPacket] = (SRPCEchoer_RpcStreamStream)(nil)
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.13.0
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 -= len(m.unknownFields)
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 -= len(m.Body)
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
- l = len(m.Body)
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 strings.Builder
173
- sb.WriteString("EchoMsg {")
164
+ var sb protobuf_go_lite.TextBuilder
165
+ initialLen := protobuf_go_lite.TextStartMessage(&sb, "EchoMsg")
174
166
  if x.Body != "" {
175
- if sb.Len() > 9 {
176
- sb.WriteString(" ")
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
- sb.WriteString("}")
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 stringLen uint64
214
- stringLen, iNdEx, err = protobuf_go_lite.DecodeVarint(dAtA, iNdEx)
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
- intStringLen := int(stringLen)
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:])
@@ -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.7
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"
@@ -1,5 +1,5 @@
1
1
  // Code generated by protoc-gen-srpc. DO NOT EDIT.
2
- // protoc-gen-srpc version: v0.49.7
2
+ // protoc-gen-srpc version: v0.49.21
3
3
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
4
4
 
5
5
  package echo
@@ -1,7 +1,7 @@
1
1
  //go:build deps_only && cgo
2
2
 
3
3
  // Code generated by protoc-gen-starpc-cpp. DO NOT EDIT.
4
- // protoc-gen-starpc-cpp version: v0.49.7
4
+ // protoc-gen-starpc-cpp version: v0.49.21
5
5
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
6
6
 
7
7
  #pragma once
@@ -1,5 +1,5 @@
1
1
  // Code generated by protoc-gen-starpc-rust. DO NOT EDIT.
2
- // protoc-gen-starpc-rust version: v0.49.7
2
+ // protoc-gen-starpc-rust version: v0.49.21
3
3
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
4
4
 
5
5
  #[allow(unused_imports)]
package/echo/server.go CHANGED
@@ -94,4 +94,4 @@ func (s *EchoServer) DoNothing(ctx context.Context, empty *emptypb.Empty) (*empt
94
94
  }
95
95
 
96
96
  // _ is a type assertion
97
- var _ SRPCEchoerServer = ((*EchoServer)(nil))
97
+ var _ SRPCEchoerServer = (*EchoServer)(nil)
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.34.4 // latest
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
- receiptMode := len(os.Args) > 1 && os.Args[1] == "receipt"
19
- receiptCase := ""
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[addrIndex]
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
- var err error
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
- var receiptDone <-chan struct{}
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
- var stream net.Conn
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
  }