starpc 0.49.20 → 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 (78) hide show
  1. package/dist/integration/cross-language/ts-client.js +5 -79
  2. package/dist/integration/cross-language/ts-server.js +4 -84
  3. package/dist/mock/index.d.ts +2 -2
  4. package/dist/mock/index.js +2 -2
  5. package/dist/srpc/client.d.ts +1 -3
  6. package/dist/srpc/client.js +2 -22
  7. package/dist/srpc/common-rpc.d.ts +1 -11
  8. package/dist/srpc/common-rpc.js +5 -66
  9. package/dist/srpc/handler.d.ts +1 -2
  10. package/dist/srpc/index.d.ts +0 -4
  11. package/dist/srpc/index.js +0 -2
  12. package/dist/srpc/invoker.d.ts +1 -2
  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 +13 -12
  23. package/go.sum +26 -24
  24. package/integration/cross-language/go-client/main.go +4 -142
  25. package/integration/cross-language/go-server/main.go +3 -149
  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 +6 -94
  29. package/mock/index.ts +2 -2
  30. package/mock/mock.go +1 -1
  31. package/mock/mock.pb.go +12 -36
  32. package/mock/mock_srpc.pb.cpp +1 -1
  33. package/mock/mock_srpc.pb.go +1 -1
  34. package/mock/mock_srpc.pb.hpp +1 -1
  35. package/mock/mock_srpc.pb.rs +1 -1
  36. package/package.json +19 -26
  37. package/srpc/accept.go +1 -1
  38. package/srpc/client-prefix.go +1 -1
  39. package/srpc/client-set.go +1 -1
  40. package/srpc/client.go +1 -1
  41. package/srpc/client.ts +4 -31
  42. package/srpc/common-rpc.go +15 -69
  43. package/srpc/common-rpc.ts +6 -78
  44. package/srpc/common-rpc_test.go +81 -0
  45. package/srpc/errors.go +26 -1
  46. package/srpc/handler.ts +1 -2
  47. package/srpc/index.ts +0 -4
  48. package/srpc/invoker-prefix.go +1 -1
  49. package/srpc/invoker.go +2 -2
  50. package/srpc/invoker.ts +5 -9
  51. package/srpc/message.go +1 -1
  52. package/srpc/msg-stream.go +1 -9
  53. package/srpc/mux-verbose.go +1 -1
  54. package/srpc/mux.go +1 -1
  55. package/srpc/muxed-conn.go +1 -1
  56. package/srpc/muxed-yamux.go +1 -1
  57. package/srpc/rpcproto.pb.go +102 -323
  58. package/srpc/server-rpc.go +1 -1
  59. package/srpc/server-rpc.ts +1 -6
  60. package/srpc/server.test.ts +49 -1
  61. package/srpc/stream-pipe.go +1 -1
  62. package/srpc/stream-rwc.go +1 -1
  63. package/srpc/stream-yamux.go +1 -1
  64. package/srpc/stream.go +0 -20
  65. package/srpc/websocket.go +1 -1
  66. package/dist/srpc/call-receipt.d.ts +0 -17
  67. package/dist/srpc/call-receipt.js +0 -105
  68. package/dist/srpc/call-receipt.test.d.ts +0 -1
  69. package/dist/srpc/call-receipt.test.js +0 -374
  70. package/dist/srpc/server-invocation.d.ts +0 -17
  71. package/dist/srpc/server-invocation.js +0 -37
  72. package/srpc/call-receipt-e2e_test.go +0 -111
  73. package/srpc/call-receipt.go +0 -112
  74. package/srpc/call-receipt.test.ts +0 -438
  75. package/srpc/call-receipt.ts +0 -130
  76. package/srpc/call-receipt_test.go +0 -536
  77. package/srpc/server-invocation.go +0 -58
  78. package/srpc/server-invocation.ts +0 -82
@@ -1,130 +0,0 @@
1
- /// <reference lib="es2024.promise" />
2
- import { ERR_RPC_ABORT } from './errors.js'
3
- import { ClientRPC } from './client-rpc.js'
4
-
5
- // ReceiptRpc exposes held unary calls without widening ProtoRpc.
6
- export interface ReceiptRpc {
7
- requestWithReceipt(
8
- service: string,
9
- method: string,
10
- data: Uint8Array,
11
- abortSignal?: AbortSignal,
12
- ): Promise<HeldCall>
13
- }
14
-
15
- // HeldCall contains the first response and its held terminal receipt.
16
- export interface HeldCall {
17
- readonly response: Uint8Array
18
- readonly receipt: CallReceipt
19
- }
20
-
21
- // CallReceipt holds a unary call until it is committed or aborted.
22
- export class CallReceipt {
23
- #call: ClientRPC
24
- #iterator: AsyncIterator<Uint8Array>
25
- #terminalPromise: Promise<IteratorResult<Uint8Array>>
26
- #terminal?: 'committed' | 'aborted'
27
- #done: Promise<void>
28
- #requestCommitted = false
29
- #resolveDone!: () => void
30
- #rejectDone!: (reason?: unknown) => void
31
-
32
- public constructor(call: ClientRPC, iterator: AsyncIterator<Uint8Array>) {
33
- this.#call = call
34
- this.#iterator = iterator
35
- const { promise, resolve, reject } = Promise.withResolvers<void>()
36
- this.#done = promise
37
- this.#resolveDone = resolve
38
- this.#rejectDone = reject
39
- this.#done.catch(() => undefined)
40
- this.#terminalPromise = this.#observeTerminal()
41
- this.#terminalPromise.catch(() => undefined)
42
- }
43
-
44
- async #observeTerminal(): Promise<IteratorResult<Uint8Array>> {
45
- try {
46
- const result = await this.#iterator.next()
47
- if (!result.done) {
48
- throw new Error('unexpected trailing response data')
49
- }
50
- const terminal = this.#call.getTerminalKind()
51
- if (
52
- terminal !== 'committed' ||
53
- !this.#requestCommitted ||
54
- this.#terminal !== 'committed'
55
- ) {
56
- throw new Error('receipt closed before commit')
57
- }
58
- return result
59
- } catch (err) {
60
- this.#rejectDone(err)
61
- const error =
62
- err instanceof Error ? err : new Error('receipt terminal failed')
63
- try {
64
- await this.#call.close(error)
65
- } catch {
66
- // The primary terminal error is already recorded on the receipt.
67
- }
68
- throw err
69
- }
70
- }
71
-
72
- // done resolves after committed close and rejects on terminal failure.
73
- public get done(): Promise<void> {
74
- return this.#done
75
- }
76
-
77
- // settled reports whether a terminal transition has been claimed.
78
- public get settled(): boolean {
79
- return this.#terminal !== undefined
80
- }
81
-
82
- // commit sends request completion and waits for server finalization.
83
- public async commit(): Promise<void> {
84
- if (this.#terminal === 'aborted') {
85
- throw new Error(ERR_RPC_ABORT)
86
- }
87
- if (this.#terminal === 'committed') {
88
- return this.#done
89
- }
90
- this.#terminal = 'committed'
91
- this.#requestCommitted = true
92
- try {
93
- await this.#call.writeCallData(undefined, true)
94
- await this.#terminalPromise
95
- await this.#call.close()
96
- this.#resolveDone()
97
- } catch (err) {
98
- await this.#call.close(
99
- err instanceof Error ? err : new Error('receipt commit failed'),
100
- )
101
- this.#rejectDone(err)
102
- throw err
103
- }
104
- }
105
-
106
- // abort sends request cancellation and never rejects.
107
- public async abort(reason?: Error): Promise<void> {
108
- if (this.#terminal !== undefined) {
109
- return
110
- }
111
- this.#terminal = 'aborted'
112
- let terminalError: unknown = reason
113
- try {
114
- await this.#call.writeCallCancel(true)
115
- } catch (err) {
116
- terminalError ??= err
117
- }
118
- try {
119
- await this.#call.close()
120
- } catch (err) {
121
- terminalError ??= err
122
- }
123
- this.#rejectDone(terminalError ?? new Error(ERR_RPC_ABORT))
124
- }
125
-
126
- // asyncDispose aborts a receipt that has not reached a terminal.
127
- public async [Symbol.asyncDispose](): Promise<void> {
128
- await this.abort()
129
- }
130
- }
@@ -1,536 +0,0 @@
1
- package srpc
2
-
3
- import (
4
- "context"
5
- "errors"
6
- "io"
7
- "sync"
8
- "sync/atomic"
9
- "testing"
10
- "time"
11
-
12
- "github.com/sirupsen/logrus"
13
- )
14
-
15
- type receiptTestStream struct {
16
- ctx context.Context
17
- recvCount atomic.Int32
18
- ack <-chan error
19
- ackErr error
20
- terminal TerminalKind
21
- terminalOkay bool
22
- closeSend atomic.Int32
23
- closeCount atomic.Int32
24
- closeErr error
25
- serviceSeen string
26
- }
27
-
28
- func (s *receiptTestStream) Context() context.Context {
29
- return s.ctx
30
- }
31
-
32
- func (s *receiptTestStream) MsgSend(Message) error {
33
- return nil
34
- }
35
-
36
- func (s *receiptTestStream) MsgRecv(msg Message) error {
37
- if s.recvCount.Add(1) == 1 {
38
- return msg.UnmarshalVT([]byte("response"))
39
- }
40
- if s.ack != nil {
41
- return <-s.ack
42
- }
43
- return s.ackErr
44
- }
45
-
46
- func (s *receiptTestStream) CloseSend() error {
47
- s.closeSend.Add(1)
48
- return nil
49
- }
50
-
51
- func (s *receiptTestStream) Close() error {
52
- s.closeCount.Add(1)
53
- return s.closeErr
54
- }
55
-
56
- func (s *receiptTestStream) receiptTerminalKind() (TerminalKind, bool) {
57
- return s.terminal, s.terminalOkay
58
- }
59
-
60
- type receiptTestClient struct {
61
- stream *receiptTestStream
62
- }
63
-
64
- func (c *receiptTestClient) ExecCall(context.Context, string, string, Message, Message) error {
65
- return nil
66
- }
67
-
68
- func (c *receiptTestClient) NewStream(
69
- _ context.Context,
70
- service, _ string,
71
- _ Message,
72
- ) (Stream, error) {
73
- c.stream.serviceSeen = service
74
- return c.stream, nil
75
- }
76
-
77
- func TestCallReceiptCommitWaitsForServerAcknowledgment(t *testing.T) {
78
- ack := make(chan error, 1)
79
- stream := &receiptTestStream{ctx: context.Background(), ack: ack}
80
- client := &receiptTestClient{stream: stream}
81
- out := NewRawMessage(nil, true)
82
- receipt, err := ExecCallReceipt(
83
- context.Background(), client, "service", "method", NewRawMessage(nil, true), out,
84
- )
85
- if err != nil {
86
- t.Fatalf("exec receipt: %v", err)
87
- }
88
-
89
- commitDone := make(chan error, 1)
90
- go func() { commitDone <- receipt.Commit() }()
91
- select {
92
- case err := <-commitDone:
93
- t.Fatalf("commit returned before acknowledgment: %v", err)
94
- case <-time.After(time.Millisecond):
95
- }
96
- if got := stream.closeSend.Load(); got != 1 {
97
- t.Fatalf("close send count = %d, want 1", got)
98
- }
99
-
100
- stream.terminal = TerminalCommitted
101
- stream.terminalOkay = true
102
- ack <- io.EOF
103
- select {
104
- case err := <-commitDone:
105
- if err != nil {
106
- t.Fatalf("commit: %v", err)
107
- }
108
- case <-time.After(time.Second):
109
- t.Fatal("commit did not return after acknowledgment")
110
- }
111
- if got := stream.closeCount.Load(); got != 1 {
112
- t.Fatalf("cleanup close count = %d, want 1", got)
113
- }
114
- }
115
-
116
- func TestCallReceiptRejectsBareCloseAfterCloseSend(t *testing.T) {
117
- stream := &receiptTestStream{
118
- ctx: context.Background(),
119
- ackErr: io.EOF,
120
- terminal: TerminalClosed,
121
- terminalOkay: true,
122
- }
123
- receipt, err := ExecCallReceipt(
124
- context.Background(), &receiptTestClient{stream: stream},
125
- "service", "method", NewRawMessage(nil, true), NewRawMessage(nil, true),
126
- )
127
- if err != nil {
128
- t.Fatalf("exec receipt: %v", err)
129
- }
130
- if err := receipt.Commit(); err == nil {
131
- t.Fatal("commit accepted bare close after CloseSend")
132
- }
133
- if got := stream.closeSend.Load(); got != 1 {
134
- t.Fatalf("close send count = %d, want 1", got)
135
- }
136
- }
137
-
138
- func TestCallReceiptCommitIgnoresCleanupError(t *testing.T) {
139
- stream := &receiptTestStream{
140
- ctx: context.Background(),
141
- ackErr: io.EOF,
142
- terminal: TerminalCommitted,
143
- terminalOkay: true,
144
- closeErr: ErrCompleted,
145
- }
146
- receipt, err := ExecCallReceipt(
147
- context.Background(), &receiptTestClient{stream: stream},
148
- "service", "method", NewRawMessage(nil, true), NewRawMessage(nil, true),
149
- )
150
- if err != nil {
151
- t.Fatalf("exec receipt: %v", err)
152
- }
153
- if err := receipt.Commit(); err != nil {
154
- t.Fatalf("commit = %v, want nil", err)
155
- }
156
- if got := stream.closeSend.Load(); got != 1 {
157
- t.Fatalf("close send count = %d, want 1", got)
158
- }
159
- }
160
-
161
- func TestCallReceiptCommitRejectsTrailingResponse(t *testing.T) {
162
- stream := &receiptTestStream{ctx: context.Background()}
163
- receipt, err := ExecCallReceipt(
164
- context.Background(), &receiptTestClient{stream: stream},
165
- "service", "method", NewRawMessage(nil, true), NewRawMessage(nil, true),
166
- )
167
- if err != nil {
168
- t.Fatalf("exec receipt: %v", err)
169
- }
170
- if err := receipt.Commit(); err == nil {
171
- t.Fatal("commit accepted trailing response data")
172
- }
173
- }
174
-
175
- func TestCallReceiptCommitReturnsAcknowledgmentError(t *testing.T) {
176
- ackErr := errors.New("ack transport loss")
177
- stream := &receiptTestStream{
178
- ctx: context.Background(),
179
- ackErr: ackErr,
180
- }
181
- receipt, err := ExecCallReceipt(
182
- context.Background(), &receiptTestClient{stream: stream},
183
- "service", "method", NewRawMessage(nil, true), NewRawMessage(nil, true),
184
- )
185
- if err != nil {
186
- t.Fatalf("exec receipt: %v", err)
187
- }
188
- if err := receipt.Commit(); !errors.Is(err, ackErr) {
189
- t.Fatalf("commit error = %v, want %v", err, ackErr)
190
- }
191
- }
192
-
193
- func TestCallReceiptCommitAbortExclusion(t *testing.T) {
194
- for range 100 {
195
- stream := &receiptTestStream{
196
- ctx: context.Background(),
197
- ackErr: io.EOF,
198
- terminal: TerminalCommitted,
199
- terminalOkay: true,
200
- }
201
- receipt, err := ExecCallReceipt(
202
- context.Background(), &receiptTestClient{stream: stream},
203
- "service", "method", NewRawMessage(nil, true), NewRawMessage(nil, true),
204
- )
205
- if err != nil {
206
- t.Fatalf("exec receipt: %v", err)
207
- }
208
- var wg sync.WaitGroup
209
- wg.Add(2)
210
- go func() {
211
- defer wg.Done()
212
- _ = receipt.Commit()
213
- }()
214
- go func() {
215
- defer wg.Done()
216
- _ = receipt.Abort()
217
- }()
218
- wg.Wait()
219
- if got := stream.closeCount.Load(); got != 1 {
220
- t.Fatalf("cleanup close count = %d, want 1", got)
221
- }
222
- if got := stream.closeSend.Load(); got > 1 {
223
- t.Fatalf("close send count = %d, want at most 1", got)
224
- }
225
- }
226
- }
227
-
228
- func TestExecCallReceiptPreservesNewStreamWrappers(t *testing.T) {
229
- log := logrus.New()
230
- cases := []struct {
231
- name string
232
- client Client
233
- service string
234
- expected string
235
- }{
236
- {
237
- name: "prefix",
238
- client: NewPrefixClient(newReceiptClient(), []string{"prefix/"}),
239
- service: "prefix/service",
240
- expected: "service",
241
- },
242
- {
243
- name: "set",
244
- client: NewClientSet([]Client{newReceiptClient()}),
245
- service: "service",
246
- expected: "service",
247
- },
248
- {
249
- name: "verbose",
250
- client: NewVClient(newReceiptClient(), logrus.NewEntry(log)),
251
- service: "service",
252
- expected: "service",
253
- },
254
- }
255
- for _, tc := range cases {
256
- t.Run(tc.name, func(t *testing.T) {
257
- out := NewRawMessage(nil, true)
258
- receipt, err := ExecCallReceipt(
259
- context.Background(), tc.client, tc.service, "method",
260
- NewRawMessage(nil, true), out,
261
- )
262
- if err != nil {
263
- t.Fatalf("exec receipt: %v", err)
264
- }
265
- if err := receipt.Abort(); err != nil {
266
- t.Fatalf("abort: %v", err)
267
- }
268
- var stream *receiptTestStream
269
- switch client := tc.client.(type) {
270
- case *PrefixClient:
271
- stream = client.client.(*receiptTestClient).stream
272
- case *ClientSet:
273
- stream = client.clients[0].(*receiptTestClient).stream
274
- case *VClient:
275
- stream = client.client.(*receiptTestClient).stream
276
- }
277
- if stream.serviceSeen != tc.expected {
278
- t.Fatalf("service = %q, want %q", stream.serviceSeen, tc.expected)
279
- }
280
- })
281
- }
282
- }
283
-
284
- func newReceiptClient() *receiptTestClient {
285
- return &receiptTestClient{
286
- stream: &receiptTestStream{
287
- ctx: context.Background(),
288
- ackErr: io.EOF,
289
- terminal: TerminalCommitted,
290
- terminalOkay: true,
291
- },
292
- }
293
- }
294
-
295
- func TestServerInvocationTerminalClassification(t *testing.T) {
296
- cases := []struct {
297
- name string
298
- act func(*ServerRPC)
299
- want TerminalKind
300
- }{
301
- {
302
- name: "explicit complete",
303
- act: func(rpc *ServerRPC) {
304
- if err := rpc.HandleCallData(NewCallDataPacket(nil, false, true, nil).GetCallData()); err != nil {
305
- t.Fatalf("handle complete: %v", err)
306
- }
307
- },
308
- want: TerminalCommitted,
309
- },
310
- {
311
- name: "cancel",
312
- act: func(rpc *ServerRPC) {
313
- if err := rpc.HandleCallCancel(); err != nil {
314
- t.Fatalf("handle cancel: %v", err)
315
- }
316
- },
317
- want: TerminalCanceled,
318
- },
319
- {
320
- name: "loss",
321
- act: func(rpc *ServerRPC) {
322
- rpc.HandleStreamClose(errors.New("transport loss"))
323
- },
324
- want: TerminalLost,
325
- },
326
-
327
- {
328
- name: "context canceled transport close",
329
- act: func(rpc *ServerRPC) {
330
- rpc.HandleStreamClose(context.Canceled)
331
- },
332
- want: TerminalLost,
333
- },
334
- {
335
- name: "remote error packet",
336
- act: func(rpc *ServerRPC) {
337
- if err := rpc.HandleCallData(&CallData{Error: "remote error"}); err != nil {
338
- t.Fatalf("handle remote error: %v", err)
339
- }
340
- },
341
- want: TerminalLost,
342
- },
343
- {
344
- name: "remote error completion packet",
345
- act: func(rpc *ServerRPC) {
346
- if err := rpc.HandleCallData(&CallData{
347
- Complete: true,
348
- Error: "remote error",
349
- }); err != nil {
350
- t.Fatalf("handle remote error completion: %v", err)
351
- }
352
- },
353
- want: TerminalLost,
354
- },
355
-
356
- {
357
- name: "bare close",
358
- act: func(rpc *ServerRPC) {
359
- rpc.HandleStreamClose(nil)
360
- },
361
- want: TerminalClosed,
362
- },
363
- }
364
- for _, tc := range cases {
365
- t.Run(tc.name, func(t *testing.T) {
366
- rpc := NewServerRPC(context.Background(), nil, &closeCountingPacketWriter{})
367
- result := make(chan TerminalKind, 1)
368
- go func() {
369
- kind, err := rpc.WaitTerminal(context.Background())
370
- if err != nil {
371
- t.Errorf("wait terminal: %v", err)
372
- }
373
- result <- kind
374
- }()
375
- tc.act(rpc)
376
- select {
377
- case got := <-result:
378
- if got != tc.want {
379
- t.Fatalf("terminal = %v, want %v", got, tc.want)
380
- }
381
- case <-time.After(time.Second):
382
- t.Fatal("wait terminal did not return")
383
- }
384
- if tc.name == "bare close" && rpc.remoteCompleted {
385
- t.Fatal("bare close marked remote completion")
386
- }
387
- if tc.name == "explicit complete" && !rpc.remoteCompleted {
388
- t.Fatal("explicit completion did not mark remote completion")
389
- }
390
- if tc.name == "remote error packet" && rpc.remoteCompleted {
391
- t.Fatal("remote error packet marked remote completion")
392
- }
393
- if tc.name == "remote error completion packet" && rpc.remoteCompleted {
394
- t.Fatal("remote error completion packet marked remote completion")
395
- }
396
- })
397
- }
398
- }
399
-
400
- func TestServerInvocationTerminalIsMonotonic(t *testing.T) {
401
- cases := []struct {
402
- name string
403
- act func(*ServerRPC)
404
- }{
405
- {
406
- name: "completion then transport loss",
407
- act: func(rpc *ServerRPC) {
408
- _ = rpc.HandleCallData(
409
- NewCallDataPacket(nil, false, true, nil).GetCallData(),
410
- )
411
- rpc.HandleStreamClose(errors.New("transport loss"))
412
- },
413
- },
414
- {
415
- name: "completion then cancel",
416
- act: func(rpc *ServerRPC) {
417
- _ = rpc.HandleCallData(
418
- NewCallDataPacket(nil, false, true, nil).GetCallData(),
419
- )
420
- _ = rpc.HandleCallCancel()
421
- },
422
- },
423
- }
424
- for _, tc := range cases {
425
- t.Run(tc.name, func(t *testing.T) {
426
- rpc := NewServerRPC(context.Background(), nil, &closeCountingPacketWriter{})
427
- tc.act(rpc)
428
- kind, err := rpc.WaitTerminal(context.Background())
429
- if err != nil {
430
- t.Fatalf("wait terminal: %v", err)
431
- }
432
- if kind != TerminalCommitted {
433
- t.Fatalf("terminal = %v, want committed", kind)
434
- }
435
- })
436
- }
437
- }
438
-
439
- func TestServerInvocationGenuineAbandonment(t *testing.T) {
440
- rpc := NewServerRPC(context.Background(), nil, &closeCountingPacketWriter{})
441
- ownerCtx, ownerCancel := context.WithCancel(context.Background())
442
- result := make(chan TerminalKind, 1)
443
- go func() {
444
- kind, err := rpc.WaitTerminal(ownerCtx)
445
- if !errors.Is(err, context.Canceled) {
446
- return
447
- }
448
- result <- kind
449
- }()
450
- ownerCancel()
451
- select {
452
- case got := <-result:
453
- if got != TerminalAbandoned {
454
- t.Fatalf("terminal = %v, want %v", got, TerminalAbandoned)
455
- }
456
- case <-time.After(time.Second):
457
- t.Fatal("wait terminal did not return on owner cancellation")
458
- }
459
- }
460
-
461
- func TestServerInvocationTerminalPrecedesOwnerCancellation(t *testing.T) {
462
- cases := []struct {
463
- name string
464
- act func(*ServerRPC)
465
- want TerminalKind
466
- }{
467
- {
468
- name: "explicit complete",
469
- act: func(rpc *ServerRPC) {
470
- if err := rpc.HandleCallData(NewCallDataPacket(nil, false, true, nil).GetCallData()); err != nil {
471
- t.Fatalf("handle complete: %v", err)
472
- }
473
- },
474
- want: TerminalCommitted,
475
- },
476
- {
477
- name: "cancel",
478
- act: func(rpc *ServerRPC) {
479
- if err := rpc.HandleCallCancel(); err != nil {
480
- t.Fatalf("handle cancel: %v", err)
481
- }
482
- },
483
- want: TerminalCanceled,
484
- },
485
- {
486
- name: "loss",
487
- act: func(rpc *ServerRPC) {
488
- rpc.HandleStreamClose(errors.New("transport loss"))
489
- },
490
- want: TerminalLost,
491
- },
492
- {
493
- name: "bare close",
494
- act: func(rpc *ServerRPC) {
495
- rpc.HandleStreamClose(nil)
496
- },
497
- want: TerminalClosed,
498
- },
499
- }
500
- for _, tc := range cases {
501
- t.Run(tc.name, func(t *testing.T) {
502
- rpc := NewServerRPC(context.Background(), nil, &closeCountingPacketWriter{})
503
- ownerCtx, ownerCancel := context.WithCancel(context.Background())
504
- tc.act(rpc)
505
- ownerCancel()
506
-
507
- kind, err := rpc.WaitTerminal(ownerCtx)
508
- if err != nil {
509
- t.Fatalf("wait terminal: %v", err)
510
- }
511
- if kind != tc.want {
512
- t.Fatalf("terminal = %v, want %v", kind, tc.want)
513
- }
514
- })
515
- }
516
- }
517
-
518
- func TestServerInvocationAccessor(t *testing.T) {
519
- found := make(chan bool, 1)
520
- rpc := NewServerRPC(context.Background(), InvokerFunc(func(_, _ string, strm Stream) (bool, error) {
521
- _, ok := GetServerInvocation(strm.Context())
522
- found <- ok
523
- return true, nil
524
- }), &closeCountingPacketWriter{})
525
- if err := rpc.HandleCallStart(NewCallStartPacket("service", "method", nil, false).GetCallStart()); err != nil {
526
- t.Fatalf("handle start: %v", err)
527
- }
528
- select {
529
- case ok := <-found:
530
- if !ok {
531
- t.Fatal("server invocation missing from stream context")
532
- }
533
- case <-time.After(time.Second):
534
- t.Fatal("invoker did not run")
535
- }
536
- }
@@ -1,58 +0,0 @@
1
- package srpc
2
-
3
- import (
4
- "context"
5
- )
6
-
7
- // TerminalKind is the terminal state of a held unary invocation.
8
- type TerminalKind int
9
-
10
- const (
11
- // TerminalCommitted identifies an explicit remote CallData completion.
12
- TerminalCommitted TerminalKind = iota
13
- // TerminalCanceled identifies a remote CallCancel packet.
14
- TerminalCanceled
15
- // TerminalLost identifies a remote error or transport failure.
16
- TerminalLost
17
- // TerminalClosed identifies a bare remote close without completion.
18
- TerminalClosed
19
- // TerminalAbandoned identifies owner-context expiry without a remote terminal.
20
- TerminalAbandoned
21
- )
22
-
23
- // ServerInvocation exposes the terminal of a held unary server invocation.
24
- type ServerInvocation interface {
25
- // WaitTerminal waits for a remote terminal or owner-context expiry. The
26
- // owner context must outlive the invocation context.
27
- WaitTerminal(ownerCtx context.Context) (TerminalKind, error)
28
- // Done returns the invocation context cancellation channel for diagnostics.
29
- Done() <-chan struct{}
30
- }
31
-
32
- type serverInvocationKey struct{}
33
-
34
- func withServerInvocation(ctx context.Context, invocation ServerInvocation) context.Context {
35
- return context.WithValue(ctx, serverInvocationKey{}, invocation)
36
- }
37
-
38
- // GetServerInvocation returns the held unary invocation attached to a context.
39
- func GetServerInvocation(ctx context.Context) (ServerInvocation, bool) {
40
- if ctx == nil {
41
- return nil, false
42
- }
43
- invocation, ok := ctx.Value(serverInvocationKey{}).(ServerInvocation)
44
- return invocation, ok
45
- }
46
-
47
- // WaitTerminal waits for and classifies the terminal of a held unary invocation.
48
- func (r *ServerRPC) WaitTerminal(ownerCtx context.Context) (TerminalKind, error) {
49
- return r.commonRPC.WaitTerminal(ownerCtx)
50
- }
51
-
52
- // Done returns the invocation context cancellation channel for diagnostics.
53
- func (r *ServerRPC) Done() <-chan struct{} {
54
- return r.ctx.Done()
55
- }
56
-
57
- // _ is a type assertion.
58
- var _ ServerInvocation = (*ServerRPC)(nil)