starpc 0.50.0 → 0.52.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 (100) hide show
  1. package/cmd/protoc-gen-es-starpc/typescript.ts +37 -0
  2. package/dist/cmd/protoc-gen-es-starpc/typescript.js +24 -0
  3. package/dist/echo/echo_srpc.pb.d.ts +44 -1
  4. package/dist/echo/server.d.ts +9 -8
  5. package/dist/echo/server.js +6 -6
  6. package/dist/integration/cross-language/ts-client.js +5 -79
  7. package/dist/integration/cross-language/ts-server.js +4 -100
  8. package/dist/mock/mock_srpc.pb.d.ts +14 -1
  9. package/dist/srpc/channel.js +6 -3
  10. package/dist/srpc/channel.test.js +20 -1
  11. package/dist/srpc/client.d.ts +1 -3
  12. package/dist/srpc/client.js +2 -22
  13. package/dist/srpc/common-rpc.d.ts +1 -9
  14. package/dist/srpc/common-rpc.js +4 -62
  15. package/dist/srpc/handler.d.ts +12 -4
  16. package/dist/srpc/index.d.ts +2 -4
  17. package/dist/srpc/index.js +1 -3
  18. package/dist/srpc/invoker.d.ts +2 -2
  19. package/dist/srpc/invoker.js +2 -2
  20. package/dist/srpc/rpcproto.pb.d.ts +0 -44
  21. package/dist/srpc/rpcproto.pb.js +0 -53
  22. package/dist/srpc/server-context.d.ts +11 -0
  23. package/dist/srpc/server-context.js +28 -0
  24. package/dist/srpc/server-rpc.js +3 -3
  25. package/dist/srpc/server.test.js +65 -2
  26. package/dist/srpc/watchdog.test.js +1 -0
  27. package/echo/echo.go +8 -8
  28. package/echo/echo.pb.go +12 -36
  29. package/echo/echo_srpc.pb.cpp +1 -1
  30. package/echo/echo_srpc.pb.go +1 -1
  31. package/echo/echo_srpc.pb.hpp +1 -1
  32. package/echo/echo_srpc.pb.rs +1 -1
  33. package/echo/echo_srpc.pb.ts +74 -0
  34. package/echo/server.go +1 -1
  35. package/echo/server.ts +24 -5
  36. package/go.mod +1 -1
  37. package/go.sum +2 -0
  38. package/integration/cross-language/go-client/main.go +4 -142
  39. package/integration/cross-language/go-server/main.go +2 -170
  40. package/integration/cross-language/run.bash +5 -105
  41. package/integration/cross-language/ts-client.ts +5 -90
  42. package/integration/cross-language/ts-server.ts +4 -112
  43. package/mock/mock.go +1 -1
  44. package/mock/mock.pb.go +12 -36
  45. package/mock/mock_srpc.pb.cpp +1 -1
  46. package/mock/mock_srpc.pb.go +1 -1
  47. package/mock/mock_srpc.pb.hpp +1 -1
  48. package/mock/mock_srpc.pb.rs +1 -1
  49. package/mock/mock_srpc.pb.ts +19 -1
  50. package/package.json +2 -2
  51. package/srpc/channel.test.ts +21 -1
  52. package/srpc/channel.ts +7 -3
  53. package/srpc/client-prefix.go +1 -1
  54. package/srpc/client-set.go +1 -1
  55. package/srpc/client.go +1 -1
  56. package/srpc/client.ts +4 -31
  57. package/srpc/common-rpc.go +15 -69
  58. package/srpc/common-rpc.ts +4 -77
  59. package/srpc/common-rpc_test.go +81 -0
  60. package/srpc/errors.go +26 -1
  61. package/srpc/handler.ts +54 -5
  62. package/srpc/index.ts +7 -4
  63. package/srpc/invoker-prefix.go +1 -1
  64. package/srpc/invoker.go +2 -2
  65. package/srpc/invoker.ts +20 -7
  66. package/srpc/message.go +1 -1
  67. package/srpc/msg-stream.go +1 -9
  68. package/srpc/mux-verbose.go +1 -1
  69. package/srpc/mux.go +1 -1
  70. package/srpc/rpcproto.pb.cc +4 -15
  71. package/srpc/rpcproto.pb.go +102 -419
  72. package/srpc/rpcproto.pb.h +0 -59
  73. package/srpc/rpcproto.pb.rs +0 -45
  74. package/srpc/rpcproto.pb.ts +0 -62
  75. package/srpc/rpcproto.proto +0 -16
  76. package/srpc/server-context.ts +55 -0
  77. package/srpc/server-rpc.go +1 -1
  78. package/srpc/server-rpc.ts +4 -6
  79. package/srpc/server.test.ts +94 -1
  80. package/srpc/stream-pipe.go +1 -1
  81. package/srpc/stream-rwc.go +1 -1
  82. package/srpc/stream.go +0 -20
  83. package/srpc/watchdog.test.ts +1 -0
  84. package/dist/rpcstream/receipt.test.d.ts +0 -1
  85. package/dist/rpcstream/receipt.test.js +0 -41
  86. package/dist/srpc/call-receipt.d.ts +0 -17
  87. package/dist/srpc/call-receipt.js +0 -106
  88. package/dist/srpc/call-receipt.test.d.ts +0 -1
  89. package/dist/srpc/call-receipt.test.js +0 -375
  90. package/dist/srpc/server-invocation.d.ts +0 -17
  91. package/dist/srpc/server-invocation.js +0 -37
  92. package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
  93. package/srpc/call-receipt-e2e_test.go +0 -111
  94. package/srpc/call-receipt.go +0 -112
  95. package/srpc/call-receipt.test.ts +0 -441
  96. package/srpc/call-receipt.ts +0 -131
  97. package/srpc/call-receipt_test.go +0 -536
  98. package/srpc/schema-ownership_test.go +0 -115
  99. package/srpc/server-invocation.go +0 -40
  100. package/srpc/server-invocation.ts +0 -76
@@ -11,6 +11,7 @@ import {
11
11
  buildEncodeMessageTransform,
12
12
  MessageStream,
13
13
  ProtoRpc,
14
+ ServerContext,
14
15
  } from 'starpc'
15
16
 
16
17
  /**
@@ -151,6 +152,79 @@ export interface Echoer {
151
152
  DoNothing(request: Empty, abortSignal?: AbortSignal): Promise<Empty>
152
153
  }
153
154
 
155
+ /**
156
+ * Echoer service returns the given message.
157
+ *
158
+ * @generated from service echo.Echoer
159
+ */
160
+ export interface EchoerHandler {
161
+ /**
162
+ * Echo returns the given message.
163
+ *
164
+ * @generated from rpc echo.Echoer.Echo
165
+ */
166
+ Echo(
167
+ request: EchoMsg,
168
+ abortSignal: AbortSignal,
169
+ context: ServerContext,
170
+ ): Promise<EchoMsg>
171
+
172
+ /**
173
+ * EchoServerStream is an example of a server -> client one-way stream.
174
+ *
175
+ * @generated from rpc echo.Echoer.EchoServerStream
176
+ */
177
+ EchoServerStream(
178
+ request: EchoMsg,
179
+ abortSignal: AbortSignal,
180
+ context: ServerContext,
181
+ ): MessageStream<EchoMsg>
182
+
183
+ /**
184
+ * EchoClientStream is an example of client->server one-way stream.
185
+ *
186
+ * @generated from rpc echo.Echoer.EchoClientStream
187
+ */
188
+ EchoClientStream(
189
+ request: MessageStream<EchoMsg>,
190
+ abortSignal: AbortSignal,
191
+ context: ServerContext,
192
+ ): Promise<EchoMsg>
193
+
194
+ /**
195
+ * EchoBidiStream is an example of a two-way stream.
196
+ *
197
+ * @generated from rpc echo.Echoer.EchoBidiStream
198
+ */
199
+ EchoBidiStream(
200
+ request: MessageStream<EchoMsg>,
201
+ abortSignal: AbortSignal,
202
+ context: ServerContext,
203
+ ): MessageStream<EchoMsg>
204
+
205
+ /**
206
+ * RpcStream opens a nested rpc call stream.
207
+ *
208
+ * @generated from rpc echo.Echoer.RpcStream
209
+ */
210
+ RpcStream(
211
+ request: MessageStream<RpcStreamPacket>,
212
+ abortSignal: AbortSignal,
213
+ context: ServerContext,
214
+ ): MessageStream<RpcStreamPacket>
215
+
216
+ /**
217
+ * DoNothing does nothing.
218
+ *
219
+ * @generated from rpc echo.Echoer.DoNothing
220
+ */
221
+ DoNothing(
222
+ request: Empty,
223
+ abortSignal: AbortSignal,
224
+ context: ServerContext,
225
+ ): Promise<Empty>
226
+ }
227
+
154
228
  export const EchoerServiceName = EchoerDefinition.typeName
155
229
 
156
230
  export class EchoerClient implements Echoer {
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/echo/server.ts CHANGED
@@ -3,14 +3,15 @@ import { Message } from '@aptre/protobuf-es-lite'
3
3
  import { Empty } from '@aptre/protobuf-es-lite/google/protobuf/empty'
4
4
  import { EchoMsg } from './echo.pb.js'
5
5
  import { Server } from '../srpc/server.js'
6
+ import type { ServerContext } from '../srpc/server-context.js'
6
7
  import { messagePushable, writeToPushable } from '../srpc/pushable.js'
7
8
  import { RpcStreamPacket } from '../rpcstream/rpcstream.pb.js'
8
9
  import { MessageStream } from '../srpc/message.js'
9
10
  import { handleRpcStream, RpcStreamHandler } from '../rpcstream/rpcstream.js'
10
- import { Echoer } from './echo_srpc.pb.js'
11
+ import type { EchoerHandler } from './echo_srpc.pb.js'
11
12
 
12
13
  // EchoServer implements the Echoer server.
13
- export class EchoerServer implements Echoer {
14
+ export class EchoerServer implements EchoerHandler {
14
15
  // proxyServer is the server used for RpcStream requests.
15
16
  private proxyServer?: Server
16
17
 
@@ -18,11 +19,19 @@ export class EchoerServer implements Echoer {
18
19
  this.proxyServer = proxyServer
19
20
  }
20
21
 
21
- public async Echo(request: EchoMsg): Promise<Message<EchoMsg>> {
22
+ public async Echo(
23
+ request: EchoMsg,
24
+ _abortSignal: AbortSignal,
25
+ _context: ServerContext,
26
+ ): Promise<Message<EchoMsg>> {
22
27
  return request
23
28
  }
24
29
 
25
- public async *EchoServerStream(request: EchoMsg): MessageStream<EchoMsg> {
30
+ public async *EchoServerStream(
31
+ request: EchoMsg,
32
+ _abortSignal: AbortSignal,
33
+ _context: ServerContext,
34
+ ): MessageStream<EchoMsg> {
26
35
  for (let i = 0; i < 5; i++) {
27
36
  yield request
28
37
  await new Promise((resolve) => setTimeout(resolve, 200))
@@ -31,6 +40,8 @@ export class EchoerServer implements Echoer {
31
40
 
32
41
  public async EchoClientStream(
33
42
  request: MessageStream<EchoMsg>,
43
+ _abortSignal: AbortSignal,
44
+ _context: ServerContext,
34
45
  ): Promise<Message<EchoMsg>> {
35
46
  // return the first message sent by the client.
36
47
  const message = await first(request)
@@ -42,6 +53,8 @@ export class EchoerServer implements Echoer {
42
53
 
43
54
  public EchoBidiStream(
44
55
  request: MessageStream<EchoMsg>,
56
+ _abortSignal: AbortSignal,
57
+ _context: ServerContext,
45
58
  ): MessageStream<EchoMsg> {
46
59
  // build result observable
47
60
  const result = messagePushable<EchoMsg>()
@@ -52,6 +65,8 @@ export class EchoerServer implements Echoer {
52
65
 
53
66
  public RpcStream(
54
67
  request: MessageStream<RpcStreamPacket>,
68
+ _abortSignal: AbortSignal,
69
+ _context: ServerContext,
55
70
  ): MessageStream<RpcStreamPacket> {
56
71
  return handleRpcStream(
57
72
  request[Symbol.asyncIterator](),
@@ -64,7 +79,11 @@ export class EchoerServer implements Echoer {
64
79
  )
65
80
  }
66
81
 
67
- public async DoNothing(): Promise<Empty> {
82
+ public async DoNothing(
83
+ _request: Empty,
84
+ _abortSignal: AbortSignal,
85
+ _context: ServerContext,
86
+ ): Promise<Empty> {
68
87
  return {}
69
88
  }
70
89
  }
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
  }