starpc 0.49.17 → 0.49.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/cmd/protoc-gen-es-starpc/typescript.ts +11 -6
- package/dist/cmd/protoc-gen-es-starpc/typescript.js +6 -5
- package/dist/echo/echo.pb.d.ts +1 -1
- package/dist/echo/echo.pb.js +3 -3
- package/dist/integration/cross-language/ts-client.js +84 -11
- package/dist/integration/cross-language/ts-server.js +84 -4
- package/dist/mock/mock.pb.d.ts +1 -1
- package/dist/mock/mock.pb.js +3 -3
- package/dist/rpcstream/rpcstream.pb.d.ts +1 -1
- package/dist/rpcstream/rpcstream.pb.js +14 -8
- package/dist/srpc/call-receipt.d.ts +17 -0
- package/dist/srpc/call-receipt.js +105 -0
- package/dist/srpc/call-receipt.test.d.ts +1 -0
- package/dist/srpc/call-receipt.test.js +374 -0
- package/dist/srpc/client.d.ts +3 -1
- package/dist/srpc/client.js +20 -0
- package/dist/srpc/common-rpc.d.ts +13 -1
- package/dist/srpc/common-rpc.js +86 -6
- package/dist/srpc/handler.d.ts +2 -1
- package/dist/srpc/index.d.ts +4 -0
- package/dist/srpc/index.js +2 -0
- package/dist/srpc/invoker.d.ts +2 -1
- package/dist/srpc/invoker.js +2 -2
- package/dist/srpc/rpcproto.pb.d.ts +1 -1
- package/dist/srpc/rpcproto.pb.js +7 -7
- package/dist/srpc/server-invocation.d.ts +17 -0
- package/dist/srpc/server-invocation.js +37 -0
- package/dist/srpc/server-rpc.js +3 -1
- package/echo/echo.pb.go +1 -1
- package/echo/echo.pb.ts +6 -5
- 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/integration/cross-language/go-client/main.go +137 -5
- package/integration/cross-language/go-server/main.go +146 -3
- package/integration/cross-language/run.bash +117 -13
- package/integration/cross-language/ts-client.ts +94 -13
- package/integration/cross-language/ts-server.ts +94 -6
- package/mock/mock.pb.go +1 -1
- package/mock/mock.pb.ts +6 -5
- 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/call-receipt-e2e_test.go +111 -0
- package/srpc/call-receipt.go +112 -0
- package/srpc/call-receipt.test.ts +438 -0
- package/srpc/call-receipt.ts +130 -0
- package/srpc/call-receipt_test.go +536 -0
- package/srpc/client-rpc.go +1 -8
- package/srpc/client.ts +29 -2
- package/srpc/common-rpc.go +134 -29
- package/srpc/common-rpc.ts +98 -8
- package/srpc/common-rpc_test.go +52 -0
- package/srpc/handler.ts +2 -0
- package/srpc/index.ts +4 -0
- package/srpc/invoker.ts +10 -5
- package/srpc/msg-stream.go +8 -0
- package/srpc/muxed-conn.go +8 -3
- package/srpc/muxed-conn_test.go +79 -0
- package/srpc/rpcproto.pb.go +1 -1
- package/srpc/rpcproto.pb.ts +28 -27
- package/srpc/server-invocation.go +58 -0
- package/srpc/server-invocation.ts +82 -0
- package/srpc/server-rpc-invoke.go +7 -0
- package/srpc/server-rpc-invoke_goscript.go +12 -0
- package/srpc/server-rpc-invoke_goscript_test.go +27 -0
- package/srpc/server-rpc.go +9 -2
- package/srpc/server-rpc.ts +6 -2
- package/srpc/stream.go +20 -0
package/dist/srpc/rpcproto.pb.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// @generated by protoc-gen-es-lite unknown with parameter "target=ts,ts_nocheck=false"
|
|
2
2
|
// @generated from file github.com/aperturerobotics/starpc/srpc/rpcproto.proto (package srpc, syntax proto3)
|
|
3
3
|
/* eslint-disable */
|
|
4
|
-
import { createMessageType
|
|
4
|
+
import { createMessageType } from '@aptre/protobuf-es-lite/message';
|
|
5
|
+
import { ScalarType } from '@aptre/protobuf-es-lite/scalar';
|
|
5
6
|
export const protobufPackage = 'srpc';
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
export const CallStart =
|
|
8
|
+
/* @__PURE__ */ createMessageType({
|
|
8
9
|
typeName: 'srpc.CallStart',
|
|
9
10
|
fields: [
|
|
10
11
|
{ no: 1, name: 'rpc_service', kind: 'scalar', T: ScalarType.STRING },
|
|
@@ -14,8 +15,8 @@ export const CallStart = createMessageType({
|
|
|
14
15
|
],
|
|
15
16
|
packedByDefault: true,
|
|
16
17
|
});
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
export const CallData =
|
|
19
|
+
/* @__PURE__ */ createMessageType({
|
|
19
20
|
typeName: 'srpc.CallData',
|
|
20
21
|
fields: [
|
|
21
22
|
{ no: 1, name: 'data', kind: 'scalar', T: ScalarType.BYTES },
|
|
@@ -25,8 +26,7 @@ export const CallData = createMessageType({
|
|
|
25
26
|
],
|
|
26
27
|
packedByDefault: true,
|
|
27
28
|
});
|
|
28
|
-
|
|
29
|
-
export const Packet = createMessageType({
|
|
29
|
+
export const Packet = /* @__PURE__ */ createMessageType({
|
|
30
30
|
typeName: 'srpc.Packet',
|
|
31
31
|
fields: [
|
|
32
32
|
{
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type TerminalKind = 'committed' | 'canceled' | 'transportLost' | 'closed' | 'abandoned';
|
|
2
|
+
export declare class ServerInvocation implements AbortSignal {
|
|
3
|
+
readonly signal: AbortSignal;
|
|
4
|
+
private readonly waitFn;
|
|
5
|
+
constructor(signal: AbortSignal, waitFn: (ownerSignal: AbortSignal) => Promise<TerminalKind>);
|
|
6
|
+
get aborted(): boolean;
|
|
7
|
+
get onabort(): typeof this.signal.onabort;
|
|
8
|
+
set onabort(value: typeof this.signal.onabort);
|
|
9
|
+
get reason(): any;
|
|
10
|
+
throwIfAborted(): void;
|
|
11
|
+
addEventListener<K extends keyof AbortSignalEventMap>(type: K, listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
12
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
13
|
+
removeEventListener<K extends keyof AbortSignalEventMap>(type: K, listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
|
|
14
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
15
|
+
dispatchEvent(event: Event): boolean;
|
|
16
|
+
waitTerminal(ownerSignal: AbortSignal): Promise<TerminalKind>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// ServerInvocation exposes the invocation signal and terminal wait.
|
|
2
|
+
export class ServerInvocation {
|
|
3
|
+
signal;
|
|
4
|
+
waitFn;
|
|
5
|
+
constructor(signal, waitFn) {
|
|
6
|
+
this.signal = signal;
|
|
7
|
+
this.waitFn = waitFn;
|
|
8
|
+
}
|
|
9
|
+
get aborted() {
|
|
10
|
+
return this.signal.aborted;
|
|
11
|
+
}
|
|
12
|
+
get onabort() {
|
|
13
|
+
return this.signal.onabort;
|
|
14
|
+
}
|
|
15
|
+
set onabort(value) {
|
|
16
|
+
this.signal.onabort = value;
|
|
17
|
+
}
|
|
18
|
+
get reason() {
|
|
19
|
+
return this.signal.reason;
|
|
20
|
+
}
|
|
21
|
+
throwIfAborted() {
|
|
22
|
+
this.signal.throwIfAborted();
|
|
23
|
+
}
|
|
24
|
+
addEventListener(type, listener, options) {
|
|
25
|
+
this.signal.addEventListener(type, listener, options);
|
|
26
|
+
}
|
|
27
|
+
removeEventListener(type, listener, options) {
|
|
28
|
+
this.signal.removeEventListener(type, listener, options);
|
|
29
|
+
}
|
|
30
|
+
dispatchEvent(event) {
|
|
31
|
+
return this.signal.dispatchEvent(event);
|
|
32
|
+
}
|
|
33
|
+
// waitTerminal waits for a remote terminal or owner-signal cancellation.
|
|
34
|
+
waitTerminal(ownerSignal) {
|
|
35
|
+
return this.waitFn(ownerSignal);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/srpc/server-rpc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CommonRPC } from './common-rpc.js';
|
|
2
|
+
import { ServerInvocation } from './server-invocation.js';
|
|
2
3
|
// ServerRPC is an ongoing RPC from the server side.
|
|
3
4
|
export class ServerRPC extends CommonRPC {
|
|
4
5
|
// lookupMethod looks up the incoming RPC methods.
|
|
@@ -37,8 +38,9 @@ export class ServerRPC extends CommonRPC {
|
|
|
37
38
|
// invokeRPC starts invoking the RPC handler.
|
|
38
39
|
async invokeRPC(invokeFn) {
|
|
39
40
|
const dataSink = this._createDataSink();
|
|
41
|
+
const invocation = new ServerInvocation(this.invocationSignal, (ownerSignal) => this.waitTerminal(ownerSignal));
|
|
40
42
|
try {
|
|
41
|
-
await invokeFn(this.rpcDataSource, dataSink);
|
|
43
|
+
await invokeFn(this.rpcDataSource, dataSink, invocation);
|
|
42
44
|
}
|
|
43
45
|
catch (err) {
|
|
44
46
|
this.close(err);
|
package/echo/echo.pb.go
CHANGED
package/echo/echo.pb.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// @generated from file github.com/aperturerobotics/starpc/echo/echo.proto (package echo, syntax proto3)
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
|
|
5
|
-
import type { MessageType
|
|
6
|
-
import { createMessageType
|
|
5
|
+
import type { MessageType } from '@aptre/protobuf-es-lite/message'
|
|
6
|
+
import { createMessageType } from '@aptre/protobuf-es-lite/message'
|
|
7
|
+
import { ScalarType } from '@aptre/protobuf-es-lite/scalar'
|
|
8
|
+
import type { PartialFieldInfo } from '@aptre/protobuf-es-lite/field'
|
|
7
9
|
|
|
8
10
|
export const protobufPackage = 'echo'
|
|
9
11
|
|
|
@@ -19,11 +21,10 @@ export interface EchoMsg {
|
|
|
19
21
|
body?: string
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
export const EchoMsg: MessageType<EchoMsg> = createMessageType({
|
|
24
|
+
export const EchoMsg: MessageType<EchoMsg> = /* @__PURE__ */ createMessageType({
|
|
24
25
|
typeName: 'echo.EchoMsg',
|
|
25
26
|
fields: [
|
|
26
27
|
{ no: 1, name: 'body', kind: 'scalar', T: ScalarType.STRING },
|
|
27
|
-
]
|
|
28
|
+
] satisfies readonly PartialFieldInfo[],
|
|
28
29
|
packedByDefault: true,
|
|
29
30
|
})
|
package/echo/echo_srpc.pb.cpp
CHANGED
package/echo/echo_srpc.pb.go
CHANGED
package/echo/echo_srpc.pb.hpp
CHANGED
package/echo/echo_srpc.pb.rs
CHANGED
|
@@ -2,6 +2,7 @@ package main
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"errors"
|
|
5
6
|
"fmt"
|
|
6
7
|
"io"
|
|
7
8
|
"net"
|
|
@@ -14,14 +15,32 @@ import (
|
|
|
14
15
|
const bodyTxt = "hello world via starpc cross-language e2e test"
|
|
15
16
|
|
|
16
17
|
func main() {
|
|
17
|
-
|
|
18
|
-
|
|
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")
|
|
19
37
|
os.Exit(1)
|
|
20
38
|
}
|
|
21
|
-
addr := os.Args[
|
|
22
|
-
|
|
39
|
+
addr := os.Args[addrIndex]
|
|
40
|
+
var conn net.Conn
|
|
23
41
|
openStream := func(ctx context.Context, msgHandler srpc.PacketDataHandler, closeHandler srpc.CloseHandler) (srpc.PacketWriter, error) {
|
|
24
|
-
|
|
42
|
+
var err error
|
|
43
|
+
conn, err = net.Dial("tcp", addr) //nolint:gosec
|
|
25
44
|
if err != nil {
|
|
26
45
|
return nil, err
|
|
27
46
|
}
|
|
@@ -33,6 +52,14 @@ func main() {
|
|
|
33
52
|
client := srpc.NewClient(openStream)
|
|
34
53
|
echoClient := echo.NewSRPCEchoerClient(client)
|
|
35
54
|
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
|
+
}
|
|
36
63
|
|
|
37
64
|
if err := testUnary(ctx, echoClient); err != nil {
|
|
38
65
|
fmt.Fprintf(os.Stderr, "unary test failed: %v\n", err)
|
|
@@ -70,6 +97,79 @@ func testUnary(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
|
70
97
|
return nil
|
|
71
98
|
}
|
|
72
99
|
|
|
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
|
+
|
|
73
173
|
func testServerStream(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
74
174
|
fmt.Println("Testing ServerStream RPC...")
|
|
75
175
|
strm, err := client.EchoServerStream(ctx, &echo.EchoMsg{Body: bodyTxt})
|
|
@@ -149,3 +249,35 @@ func testBidiStream(ctx context.Context, client echo.SRPCEchoerClient) error {
|
|
|
149
249
|
fmt.Println(" PASSED")
|
|
150
250
|
return nil
|
|
151
251
|
}
|
|
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
|
+
}
|
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"net"
|
|
7
7
|
"os"
|
|
8
8
|
"os/signal"
|
|
9
|
+
"sync"
|
|
9
10
|
|
|
10
11
|
"github.com/aperturerobotics/starpc/echo"
|
|
11
12
|
"github.com/aperturerobotics/starpc/srpc"
|
|
@@ -15,14 +16,73 @@ func main() {
|
|
|
15
16
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
16
17
|
defer cancel()
|
|
17
18
|
|
|
19
|
+
receiptMode := len(os.Args) > 1 && os.Args[1] == "receipt"
|
|
20
|
+
receiptCase := ""
|
|
21
|
+
if receiptMode {
|
|
22
|
+
if len(os.Args) < 3 {
|
|
23
|
+
fmt.Fprintln(os.Stderr, "usage: go-server receipt <case>")
|
|
24
|
+
os.Exit(1)
|
|
25
|
+
}
|
|
26
|
+
receiptCase = os.Args[2]
|
|
27
|
+
switch receiptCase {
|
|
28
|
+
case "commit", "abort", "loss", "bare-close":
|
|
29
|
+
default:
|
|
30
|
+
fmt.Fprintf(os.Stderr, "unknown receipt case: %s\n", receiptCase)
|
|
31
|
+
os.Exit(1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
18
34
|
mux := srpc.NewMux()
|
|
19
35
|
echoServer := echo.NewEchoServer(mux)
|
|
20
36
|
if err := echo.SRPCRegisterEchoer(mux, echoServer); err != nil {
|
|
21
37
|
fmt.Fprintf(os.Stderr, "register error: %v\n", err)
|
|
22
38
|
os.Exit(1)
|
|
23
39
|
}
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
var receiptDone <-chan struct{}
|
|
41
|
+
var finishReceipt func()
|
|
42
|
+
if receiptMode {
|
|
43
|
+
done := make(chan struct{})
|
|
44
|
+
var doneOnce sync.Once
|
|
45
|
+
receiptDone = done
|
|
46
|
+
finishReceipt = func() {
|
|
47
|
+
doneOnce.Do(func() {
|
|
48
|
+
close(done)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var invoker srpc.Invoker = mux
|
|
53
|
+
if receiptMode {
|
|
54
|
+
invoker = srpc.InvokerFunc(func(
|
|
55
|
+
serviceID, methodID string,
|
|
56
|
+
strm srpc.Stream,
|
|
57
|
+
) (bool, error) {
|
|
58
|
+
handled, err := mux.InvokeMethod(serviceID, methodID, strm)
|
|
59
|
+
if err != nil || !handled {
|
|
60
|
+
return handled, err
|
|
61
|
+
}
|
|
62
|
+
invocation, ok := srpc.GetServerInvocation(strm.Context())
|
|
63
|
+
if !ok {
|
|
64
|
+
return true, context.Canceled
|
|
65
|
+
}
|
|
66
|
+
kind, waitErr := invocation.WaitTerminal(context.Background())
|
|
67
|
+
markerErr := emitReceiptEvent(
|
|
68
|
+
fmt.Sprintf("SERVER_RECEIPT_TERMINAL %s", terminalName(kind)),
|
|
69
|
+
)
|
|
70
|
+
if waitErr != nil {
|
|
71
|
+
return true, waitErr
|
|
72
|
+
}
|
|
73
|
+
if markerErr != nil {
|
|
74
|
+
return true, markerErr
|
|
75
|
+
}
|
|
76
|
+
if kind != srpc.TerminalCommitted {
|
|
77
|
+
finishReceipt()
|
|
78
|
+
}
|
|
79
|
+
return true, nil
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
var server *srpc.Server
|
|
83
|
+
if !receiptMode {
|
|
84
|
+
server = srpc.NewServer(invoker)
|
|
85
|
+
}
|
|
26
86
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
27
87
|
if err != nil {
|
|
28
88
|
fmt.Fprintf(os.Stderr, "listen error: %v\n", err)
|
|
@@ -30,6 +90,13 @@ func main() {
|
|
|
30
90
|
}
|
|
31
91
|
defer ln.Close()
|
|
32
92
|
|
|
93
|
+
if receiptMode {
|
|
94
|
+
go func() {
|
|
95
|
+
<-receiptDone
|
|
96
|
+
_ = ln.Close()
|
|
97
|
+
}()
|
|
98
|
+
}
|
|
99
|
+
|
|
33
100
|
fmt.Printf("LISTENING %s\n", ln.Addr().String())
|
|
34
101
|
|
|
35
102
|
go func() {
|
|
@@ -42,6 +109,82 @@ func main() {
|
|
|
42
109
|
if err != nil {
|
|
43
110
|
return
|
|
44
111
|
}
|
|
45
|
-
|
|
112
|
+
if receiptMode {
|
|
113
|
+
go handleReceiptStream(ctx, conn, invoker, finishReceipt)
|
|
114
|
+
} else {
|
|
115
|
+
go server.HandleStream(ctx, conn)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func handleReceiptStream(
|
|
121
|
+
ctx context.Context,
|
|
122
|
+
conn net.Conn,
|
|
123
|
+
invoker srpc.Invoker,
|
|
124
|
+
finishReceipt func(),
|
|
125
|
+
) {
|
|
126
|
+
prw := srpc.NewPacketReadWriter(conn)
|
|
127
|
+
writer := &receiptPacketWriter{
|
|
128
|
+
inner: prw,
|
|
129
|
+
finishReceipt: finishReceipt,
|
|
130
|
+
}
|
|
131
|
+
rpc := srpc.NewServerRPC(ctx, invoker, writer)
|
|
132
|
+
prw.ReadPump(rpc.HandlePacketData, rpc.HandleStreamClose)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type receiptPacketWriter struct {
|
|
136
|
+
inner srpc.PacketWriter
|
|
137
|
+
finishReceipt func()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (w *receiptPacketWriter) WritePacket(pkt *srpc.Packet) error {
|
|
141
|
+
if err := w.inner.WritePacket(pkt); err != nil {
|
|
142
|
+
return err
|
|
143
|
+
}
|
|
144
|
+
data := pkt.GetCallData()
|
|
145
|
+
if data != nil && data.GetComplete() && data.GetError() == "" {
|
|
146
|
+
if err := emitReceiptEvent("SERVER_RECEIPT_ACK committed"); err != nil {
|
|
147
|
+
return err
|
|
148
|
+
}
|
|
149
|
+
w.finishReceipt()
|
|
150
|
+
}
|
|
151
|
+
return nil
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func (w *receiptPacketWriter) Close() error {
|
|
155
|
+
return w.inner.Close()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
func terminalName(kind srpc.TerminalKind) string {
|
|
159
|
+
switch kind {
|
|
160
|
+
case srpc.TerminalCommitted:
|
|
161
|
+
return "committed"
|
|
162
|
+
case srpc.TerminalCanceled:
|
|
163
|
+
return "canceled"
|
|
164
|
+
case srpc.TerminalLost:
|
|
165
|
+
return "transportLost"
|
|
166
|
+
case srpc.TerminalClosed:
|
|
167
|
+
return "closed"
|
|
168
|
+
case srpc.TerminalAbandoned:
|
|
169
|
+
return "abandoned"
|
|
170
|
+
default:
|
|
171
|
+
return "unknown"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func emitReceiptEvent(line string) error {
|
|
176
|
+
fmt.Println(line)
|
|
177
|
+
fifo := os.Getenv("RECEIPT_EVENT_FIFO")
|
|
178
|
+
if fifo == "" {
|
|
179
|
+
return nil
|
|
180
|
+
}
|
|
181
|
+
file, err := os.OpenFile(fifo, os.O_WRONLY, 0) //nolint:gosec // FIFO path is created by the local integration runner.
|
|
182
|
+
if err != nil {
|
|
183
|
+
return err
|
|
184
|
+
}
|
|
185
|
+
if _, err := file.WriteString(line + "\n"); err != nil {
|
|
186
|
+
_ = file.Close()
|
|
187
|
+
return err
|
|
46
188
|
}
|
|
189
|
+
return file.Close()
|
|
47
190
|
}
|