starpc 0.25.4 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/e2e/e2e.js +6 -2
- package/dist/echo/client-test.js +36 -16
- package/dist/srpc/channel.js +1 -0
- package/dist/srpc/client.js +3 -9
- package/dist/srpc/common-rpc.d.ts +1 -1
- package/dist/srpc/common-rpc.js +7 -9
- package/dist/srpc/pushable.js +2 -1
- package/dist/srpc/watchdog.js +1 -0
- package/e2e/e2e.ts +7 -2
- package/echo/client-test.ts +37 -18
- package/go.mod +1 -1
- package/go.sum +2 -0
- package/package.json +2 -2
- package/srpc/channel.ts +1 -0
- package/srpc/client-prefix.go +1 -1
- package/srpc/client-rpc.go +1 -1
- package/srpc/client-set.go +1 -1
- package/srpc/client-verbose.go +1 -1
- package/srpc/client.go +3 -3
- package/srpc/client.ts +3 -9
- package/srpc/common-rpc.go +1 -1
- package/srpc/common-rpc.ts +9 -11
- package/srpc/muxed-conn.go +1 -1
- package/srpc/packet-rw.go +1 -1
- package/srpc/packet.go +2 -7
- package/srpc/pushable.ts +2 -1
- package/srpc/server-pipe.go +1 -1
- package/srpc/server-rpc.go +1 -1
- package/srpc/watchdog.ts +1 -0
- package/srpc/writer.go +2 -4
- package/srpc/raw-stream-rwc.go +0 -161
package/dist/e2e/e2e.js
CHANGED
|
@@ -13,7 +13,7 @@ async function runRPC() {
|
|
|
13
13
|
const serverConn = new StreamConn(server, { direction: 'inbound' });
|
|
14
14
|
// pipe clientConn -> messageStream -> serverConn -> messageStream -> clientConn
|
|
15
15
|
const { port1: clientPort, port2: serverPort } = new MessageChannel();
|
|
16
|
-
const opts = { idleTimeoutMs: 250, keepAliveMs: 100 }
|
|
16
|
+
const opts = {}; // { idleTimeoutMs: 250, keepAliveMs: 100 }
|
|
17
17
|
const clientChannelStream = new ChannelStream('client', clientPort, opts);
|
|
18
18
|
const serverChannelStream = new ChannelStream('server', serverPort, opts);
|
|
19
19
|
// Pipe the client traffic via the client end of the MessageChannel.
|
|
@@ -30,6 +30,10 @@ async function runRPC() {
|
|
|
30
30
|
await runClientTest(client);
|
|
31
31
|
await runAbortControllerTest(client);
|
|
32
32
|
await runRpcStreamTest(client);
|
|
33
|
+
// Make sure we have no uncaught promises
|
|
34
|
+
await new Promise((resolve) => {
|
|
35
|
+
setTimeout(resolve, 500);
|
|
36
|
+
});
|
|
33
37
|
// Close cleanly
|
|
34
38
|
clientConn.close();
|
|
35
39
|
serverConn.close();
|
|
@@ -40,6 +44,6 @@ runRPC()
|
|
|
40
44
|
process.exit(0);
|
|
41
45
|
})
|
|
42
46
|
.catch((err) => {
|
|
43
|
-
console.error(err);
|
|
47
|
+
console.error('e2e tests failed', err);
|
|
44
48
|
process.exit(1);
|
|
45
49
|
});
|
package/dist/echo/client-test.js
CHANGED
|
@@ -29,24 +29,44 @@ export async function runAbortControllerTest(client) {
|
|
|
29
29
|
const demoServiceClient = new EchoerClientImpl(client);
|
|
30
30
|
console.log('Testing EchoClientStream with AbortController...');
|
|
31
31
|
let errorReturned = false;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const testRpc = async (rpc) => {
|
|
33
|
+
const clientAbort = new AbortController();
|
|
34
|
+
new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
|
|
35
|
+
clientAbort.abort();
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await rpc(clientAbort.signal);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const errMsg = err.message;
|
|
42
|
+
errorReturned = true;
|
|
43
|
+
if (errMsg !== ERR_RPC_ABORT) {
|
|
44
|
+
throw new Error('unexpected error: ' + errMsg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!errorReturned) {
|
|
48
|
+
throw new Error('expected aborted rpc to throw error');
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
await testRpc(async (signal) => {
|
|
52
|
+
const clientNoopStream = pushable({ objectMode: true });
|
|
53
|
+
await demoServiceClient.EchoClientStream(clientNoopStream, signal);
|
|
36
54
|
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
throw new Error('unexpected error: ' + errMsg);
|
|
55
|
+
await testRpc(async (signal) => {
|
|
56
|
+
const stream = demoServiceClient.EchoServerStream({ body: 'test' }, signal);
|
|
57
|
+
const msgs = [];
|
|
58
|
+
try {
|
|
59
|
+
for await (const msg of stream) {
|
|
60
|
+
msgs.push(msg);
|
|
61
|
+
}
|
|
45
62
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (msgs.length < 3) {
|
|
65
|
+
throw new Error('expected at least three messages before error');
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
50
70
|
}
|
|
51
71
|
// runRpcStreamTest tests a RPCStream.
|
|
52
72
|
export async function runRpcStreamTest(client) {
|
package/dist/srpc/channel.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ERR_STREAM_IDLE } from './errors.js';
|
|
|
7
7
|
// This implementation sends a "closed" message when close() is called.
|
|
8
8
|
// However: if the remote is removed w/o closing cleanly, the stream will be left open!
|
|
9
9
|
// Enable keepAliveMs and idleTimeoutMs to mitigate this issue with keep-alive messages.
|
|
10
|
+
// NOTE: Browsers will throttle setTimeout in background tabs.
|
|
10
11
|
export class ChannelStream {
|
|
11
12
|
// isAcked checks if the stream is acknowledged by the remote.
|
|
12
13
|
get isAcked() {
|
package/dist/srpc/client.js
CHANGED
|
@@ -28,8 +28,7 @@ export class Client {
|
|
|
28
28
|
// clientStreamingRequest starts a client side streaming request.
|
|
29
29
|
async clientStreamingRequest(service, method, data, abortSignal) {
|
|
30
30
|
const call = await this.startRpc(service, method, null, abortSignal);
|
|
31
|
-
call.writeCallDataFromSource(data)
|
|
32
|
-
.catch(err => call.close(err));
|
|
31
|
+
call.writeCallDataFromSource(data).catch((err) => call.close(err));
|
|
33
32
|
for await (const data of call.rpcDataSource) {
|
|
34
33
|
call.close();
|
|
35
34
|
return data;
|
|
@@ -42,12 +41,7 @@ export class Client {
|
|
|
42
41
|
serverStreamingRequest(service, method, data, abortSignal) {
|
|
43
42
|
const serverData = pushable({ objectMode: true });
|
|
44
43
|
this.startRpc(service, method, data, abortSignal)
|
|
45
|
-
.then(async (call) =>
|
|
46
|
-
const result = writeToPushable(call.rpcDataSource, serverData);
|
|
47
|
-
result.catch((err) => call.close(err));
|
|
48
|
-
result.then(() => call.close());
|
|
49
|
-
return result;
|
|
50
|
-
})
|
|
44
|
+
.then(async (call) => writeToPushable(call.rpcDataSource, serverData))
|
|
51
45
|
.catch((err) => serverData.end(err));
|
|
52
46
|
return serverData;
|
|
53
47
|
}
|
|
@@ -92,7 +86,7 @@ export class Client {
|
|
|
92
86
|
pipe(stream, decodePacketSource, call, encodePacketSource, stream)
|
|
93
87
|
.catch((err) => call.close(err))
|
|
94
88
|
.then(() => call.close());
|
|
95
|
-
await call.writeCallStart(data
|
|
89
|
+
await call.writeCallStart(data ?? undefined);
|
|
96
90
|
return call;
|
|
97
91
|
}
|
|
98
92
|
}
|
|
@@ -11,6 +11,7 @@ export declare class CommonRPC {
|
|
|
11
11
|
protected method?: string;
|
|
12
12
|
private closed?;
|
|
13
13
|
constructor();
|
|
14
|
+
get isClosed(): boolean | Error;
|
|
14
15
|
writeCallData(data?: Uint8Array, complete?: boolean, error?: string): Promise<void>;
|
|
15
16
|
writeCallCancel(): Promise<void>;
|
|
16
17
|
writeCallDataFromSource(dataSource: AsyncIterable<Uint8Array>): Promise<void>;
|
|
@@ -22,6 +23,5 @@ export declare class CommonRPC {
|
|
|
22
23
|
handleCallData(packet: Partial<CallData>): Promise<void>;
|
|
23
24
|
handleCallCancel(): Promise<void>;
|
|
24
25
|
close(err?: Error): Promise<void>;
|
|
25
|
-
closeWrite(): void;
|
|
26
26
|
private _createSink;
|
|
27
27
|
}
|
package/dist/srpc/common-rpc.js
CHANGED
|
@@ -16,6 +16,10 @@ export class CommonRPC {
|
|
|
16
16
|
this.source = this._source;
|
|
17
17
|
this.rpcDataSource = this._rpcDataSource;
|
|
18
18
|
}
|
|
19
|
+
// isClosed returns one of: true (closed w/o error), Error (closed w/ error), or false (not closed).
|
|
20
|
+
get isClosed() {
|
|
21
|
+
return this.closed ?? false;
|
|
22
|
+
}
|
|
19
23
|
// writeCallData writes the call data packet.
|
|
20
24
|
async writeCallData(data, complete, error) {
|
|
21
25
|
const callData = {
|
|
@@ -130,18 +134,12 @@ export class CommonRPC {
|
|
|
130
134
|
if (this.closed) {
|
|
131
135
|
return;
|
|
132
136
|
}
|
|
133
|
-
this.closed = true;
|
|
134
|
-
// note:
|
|
135
|
-
|
|
136
|
-
await this.writeCallCancel();
|
|
137
|
-
}
|
|
137
|
+
this.closed = err ?? true;
|
|
138
|
+
// note: this does nothing if _source is already ended.
|
|
139
|
+
await this.writeCallCancel();
|
|
138
140
|
this._source.end();
|
|
139
141
|
this._rpcDataSource.end(err);
|
|
140
142
|
}
|
|
141
|
-
// closeWrite closes the call for writing.
|
|
142
|
-
closeWrite() {
|
|
143
|
-
this._source.end();
|
|
144
|
-
}
|
|
145
143
|
// _createSink returns a value for the sink field.
|
|
146
144
|
_createSink() {
|
|
147
145
|
return async (source) => {
|
package/dist/srpc/pushable.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// writeToPushable writes the incoming server data to the pushable.
|
|
2
|
+
//
|
|
3
|
+
// this will not throw an error: it instead ends out w/ the error.
|
|
2
4
|
export async function writeToPushable(dataSource, out) {
|
|
3
5
|
try {
|
|
4
6
|
for await (const data of dataSource) {
|
|
@@ -8,7 +10,6 @@ export async function writeToPushable(dataSource, out) {
|
|
|
8
10
|
}
|
|
9
11
|
catch (err) {
|
|
10
12
|
out.end(err);
|
|
11
|
-
throw err;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
export function buildPushableSink(target) {
|
package/dist/srpc/watchdog.js
CHANGED
package/e2e/e2e.ts
CHANGED
|
@@ -25,7 +25,7 @@ async function runRPC() {
|
|
|
25
25
|
|
|
26
26
|
// pipe clientConn -> messageStream -> serverConn -> messageStream -> clientConn
|
|
27
27
|
const { port1: clientPort, port2: serverPort } = new MessageChannel()
|
|
28
|
-
const opts: ChannelStreamOpts = { idleTimeoutMs: 250, keepAliveMs: 100 }
|
|
28
|
+
const opts: ChannelStreamOpts = {} // { idleTimeoutMs: 250, keepAliveMs: 100 }
|
|
29
29
|
const clientChannelStream = new ChannelStream('client', clientPort, opts)
|
|
30
30
|
const serverChannelStream = new ChannelStream('server', serverPort, opts)
|
|
31
31
|
|
|
@@ -57,6 +57,11 @@ async function runRPC() {
|
|
|
57
57
|
await runAbortControllerTest(client)
|
|
58
58
|
await runRpcStreamTest(client)
|
|
59
59
|
|
|
60
|
+
// Make sure we have no uncaught promises
|
|
61
|
+
await new Promise<void>((resolve) => {
|
|
62
|
+
setTimeout(resolve, 500)
|
|
63
|
+
})
|
|
64
|
+
|
|
60
65
|
// Close cleanly
|
|
61
66
|
clientConn.close()
|
|
62
67
|
serverConn.close()
|
|
@@ -68,6 +73,6 @@ runRPC()
|
|
|
68
73
|
process.exit(0)
|
|
69
74
|
})
|
|
70
75
|
.catch((err) => {
|
|
71
|
-
console.error(err)
|
|
76
|
+
console.error('e2e tests failed', err)
|
|
72
77
|
process.exit(1)
|
|
73
78
|
})
|
package/echo/client-test.ts
CHANGED
|
@@ -36,26 +36,45 @@ export async function runAbortControllerTest(client: Client) {
|
|
|
36
36
|
|
|
37
37
|
console.log('Testing EchoClientStream with AbortController...')
|
|
38
38
|
let errorReturned = false
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
|
|
40
|
+
const testRpc = async (rpc: (signal: AbortSignal) => Promise<void>) => {
|
|
41
|
+
const clientAbort = new AbortController()
|
|
42
|
+
new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
|
|
43
|
+
clientAbort.abort()
|
|
44
|
+
})
|
|
45
|
+
try {
|
|
46
|
+
await rpc(clientAbort.signal)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const errMsg = (err as Error).message
|
|
49
|
+
errorReturned = true
|
|
50
|
+
if (errMsg !== ERR_RPC_ABORT) {
|
|
51
|
+
throw new Error('unexpected error: ' + errMsg)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!errorReturned) {
|
|
55
|
+
throw new Error('expected aborted rpc to throw error')
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
await testRpc(async (signal) => {
|
|
60
|
+
const clientNoopStream = pushable<EchoMsg>({ objectMode: true })
|
|
61
|
+
await demoServiceClient.EchoClientStream(clientNoopStream, signal)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
await testRpc(async (signal) => {
|
|
65
|
+
const stream = demoServiceClient.EchoServerStream({ body: 'test' }, signal)
|
|
66
|
+
const msgs = []
|
|
67
|
+
try {
|
|
68
|
+
for await (const msg of stream) {
|
|
69
|
+
msgs.push(msg)
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (msgs.length < 3) {
|
|
73
|
+
throw new Error('expected at least three messages before error')
|
|
74
|
+
}
|
|
75
|
+
throw err
|
|
76
|
+
}
|
|
77
|
+
})
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
// runRpcStreamTest tests a RPCStream.
|
package/go.mod
CHANGED
|
@@ -9,7 +9,7 @@ require (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
require (
|
|
12
|
-
github.com/aperturerobotics/util v1.13.
|
|
12
|
+
github.com/aperturerobotics/util v1.13.6 // latest
|
|
13
13
|
github.com/libp2p/go-libp2p v0.32.2 // latest
|
|
14
14
|
github.com/libp2p/go-yamux/v4 v4.0.2-0.20240206065824-7222fbc3459d // master
|
|
15
15
|
github.com/sirupsen/logrus v1.9.3 // latest
|
package/go.sum
CHANGED
|
@@ -4,6 +4,8 @@ github.com/aperturerobotics/util v1.13.3 h1:N4JXKYql0R/A2hMuV79SZ2vaftf8tiN8DUJ8
|
|
|
4
4
|
github.com/aperturerobotics/util v1.13.3/go.mod h1:JdziNd9tR6lWqc9bSMIe1At8Gagrg986rmtZuPCv6+w=
|
|
5
5
|
github.com/aperturerobotics/util v1.13.5 h1:g8Q9VKBUYR5Nu5Ee/ZygVbe0JkGXeNq1fBkT0ipLBMY=
|
|
6
6
|
github.com/aperturerobotics/util v1.13.5/go.mod h1:8AfpGb9RJqUItLBb5ec3sprpl9swYyHlgOw0HzkE+S8=
|
|
7
|
+
github.com/aperturerobotics/util v1.13.6 h1:8FKBjj+vs23QZBiwkGod5WUY19Cisda48Rbg1zaqzZU=
|
|
8
|
+
github.com/aperturerobotics/util v1.13.6/go.mod h1:8AfpGb9RJqUItLBb5ec3sprpl9swYyHlgOw0HzkE+S8=
|
|
7
9
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
8
10
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
9
11
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Streaming protobuf RPC service protocol over any two-way channel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"esbuild": "^0.20.0",
|
|
68
68
|
"prettier": "^3.2.4",
|
|
69
69
|
"rimraf": "^5.0.1",
|
|
70
|
-
"ts-poet": "6.
|
|
70
|
+
"ts-poet": "6.7.0",
|
|
71
71
|
"ts-proto": "^1.166.1",
|
|
72
72
|
"typescript": "^5.3.2",
|
|
73
73
|
"utf-8-validate": "^6.0.3"
|
package/srpc/channel.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface ChannelStreamOpts {
|
|
|
43
43
|
// This implementation sends a "closed" message when close() is called.
|
|
44
44
|
// However: if the remote is removed w/o closing cleanly, the stream will be left open!
|
|
45
45
|
// Enable keepAliveMs and idleTimeoutMs to mitigate this issue with keep-alive messages.
|
|
46
|
+
// NOTE: Browsers will throttle setTimeout in background tabs.
|
|
46
47
|
export class ChannelStream<T = Uint8Array>
|
|
47
48
|
implements Duplex<AsyncGenerator<T>, Source<T>, Promise<void>>
|
|
48
49
|
{
|
package/srpc/client-prefix.go
CHANGED
|
@@ -57,7 +57,7 @@ func (i *PrefixClient) stripCheckServiceIDPrefix(service string) (string, error)
|
|
|
57
57
|
// NewRawStream opens a new raw stream with the remote.
|
|
58
58
|
// Implements OpenStreamFunc.
|
|
59
59
|
// msgHandler must not be called concurrently.
|
|
60
|
-
func (i *PrefixClient) NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (
|
|
60
|
+
func (i *PrefixClient) NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (PacketWriter, error) {
|
|
61
61
|
return i.client.NewRawStream(ctx, msgHandler, closeHandler)
|
|
62
62
|
}
|
|
63
63
|
|
package/srpc/client-rpc.go
CHANGED
|
@@ -25,7 +25,7 @@ func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
|
|
|
25
25
|
|
|
26
26
|
// Start sets the writer and writes the MsgSend message.
|
|
27
27
|
// must only be called once!
|
|
28
|
-
func (r *ClientRPC) Start(writer
|
|
28
|
+
func (r *ClientRPC) Start(writer PacketWriter, writeFirstMsg bool, firstMsg []byte) error {
|
|
29
29
|
select {
|
|
30
30
|
case <-r.ctx.Done():
|
|
31
31
|
r.ctxCancel()
|
package/srpc/client-set.go
CHANGED
package/srpc/client-verbose.go
CHANGED
|
@@ -65,7 +65,7 @@ func (c *VClient) NewStream(ctx context.Context, service, method string, firstMs
|
|
|
65
65
|
// NewRawStream opens a new raw stream with the remote.
|
|
66
66
|
// Implements OpenStreamFunc.
|
|
67
67
|
// msgHandler must not be called concurrently.
|
|
68
|
-
func (c *VClient) NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (writer
|
|
68
|
+
func (c *VClient) NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (writer PacketWriter, err error) {
|
|
69
69
|
t1 := time.Now()
|
|
70
70
|
|
|
71
71
|
defer func() {
|
package/srpc/client.go
CHANGED
|
@@ -18,7 +18,7 @@ type Client interface {
|
|
|
18
18
|
// NewRawStream opens a new raw stream with the remote.
|
|
19
19
|
// Implements OpenStreamFunc.
|
|
20
20
|
// msgHandler must not be called concurrently.
|
|
21
|
-
NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (
|
|
21
|
+
NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (PacketWriter, error)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// OpenStreamFunc opens a stream with a remote.
|
|
@@ -27,7 +27,7 @@ type OpenStreamFunc = func(
|
|
|
27
27
|
ctx context.Context,
|
|
28
28
|
msgHandler PacketDataHandler,
|
|
29
29
|
closeHandler CloseHandler,
|
|
30
|
-
) (
|
|
30
|
+
) (PacketWriter, error)
|
|
31
31
|
|
|
32
32
|
// client implements Client with a transport.
|
|
33
33
|
type client struct {
|
|
@@ -102,7 +102,7 @@ func (c *client) NewRawStream(
|
|
|
102
102
|
ctx context.Context,
|
|
103
103
|
msgHandler PacketDataHandler,
|
|
104
104
|
closeHandler CloseHandler,
|
|
105
|
-
) (
|
|
105
|
+
) (PacketWriter, error) {
|
|
106
106
|
return c.openStream(ctx, msgHandler, closeHandler)
|
|
107
107
|
}
|
|
108
108
|
|
package/srpc/client.ts
CHANGED
|
@@ -48,8 +48,7 @@ export class Client implements TsProtoRpc {
|
|
|
48
48
|
abortSignal?: AbortSignal,
|
|
49
49
|
): Promise<Uint8Array> {
|
|
50
50
|
const call = await this.startRpc(service, method, null, abortSignal)
|
|
51
|
-
call.writeCallDataFromSource(data)
|
|
52
|
-
.catch(err => call.close(err))
|
|
51
|
+
call.writeCallDataFromSource(data).catch((err) => call.close(err))
|
|
53
52
|
for await (const data of call.rpcDataSource) {
|
|
54
53
|
call.close()
|
|
55
54
|
return data
|
|
@@ -68,12 +67,7 @@ export class Client implements TsProtoRpc {
|
|
|
68
67
|
): AsyncIterable<Uint8Array> {
|
|
69
68
|
const serverData: Pushable<Uint8Array> = pushable({ objectMode: true })
|
|
70
69
|
this.startRpc(service, method, data, abortSignal)
|
|
71
|
-
.then(async (call) =>
|
|
72
|
-
const result = writeToPushable(call.rpcDataSource, serverData)
|
|
73
|
-
result.catch((err) => call.close(err))
|
|
74
|
-
result.then(() => call.close())
|
|
75
|
-
return result
|
|
76
|
-
})
|
|
70
|
+
.then(async (call) => writeToPushable(call.rpcDataSource, serverData))
|
|
77
71
|
.catch((err) => serverData.end(err))
|
|
78
72
|
return serverData
|
|
79
73
|
}
|
|
@@ -129,7 +123,7 @@ export class Client implements TsProtoRpc {
|
|
|
129
123
|
pipe(stream, decodePacketSource, call, encodePacketSource, stream)
|
|
130
124
|
.catch((err) => call.close(err))
|
|
131
125
|
.then(() => call.close())
|
|
132
|
-
await call.writeCallStart(data
|
|
126
|
+
await call.writeCallStart(data ?? undefined)
|
|
133
127
|
return call
|
|
134
128
|
}
|
|
135
129
|
}
|
package/srpc/common-rpc.go
CHANGED
|
@@ -24,7 +24,7 @@ type commonRPC struct {
|
|
|
24
24
|
// bcast broadcasts when below fields change
|
|
25
25
|
bcast broadcast.Broadcast
|
|
26
26
|
// writer is the writer to write messages to
|
|
27
|
-
writer
|
|
27
|
+
writer PacketWriter
|
|
28
28
|
// dataQueue contains incoming data packets.
|
|
29
29
|
// note: packets may be len() == 0
|
|
30
30
|
dataQueue [][]byte
|
package/srpc/common-rpc.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class CommonRPC {
|
|
|
30
30
|
protected method?: string
|
|
31
31
|
|
|
32
32
|
// closed indicates this rpc has been closed already.
|
|
33
|
-
private closed?:
|
|
33
|
+
private closed?: true | Error
|
|
34
34
|
|
|
35
35
|
constructor() {
|
|
36
36
|
this.sink = this._createSink()
|
|
@@ -38,6 +38,11 @@ export class CommonRPC {
|
|
|
38
38
|
this.rpcDataSource = this._rpcDataSource
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// isClosed returns one of: true (closed w/o error), Error (closed w/ error), or false (not closed).
|
|
42
|
+
public get isClosed(): boolean | Error {
|
|
43
|
+
return this.closed ?? false
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
// writeCallData writes the call data packet.
|
|
42
47
|
public async writeCallData(
|
|
43
48
|
data?: Uint8Array,
|
|
@@ -168,20 +173,13 @@ export class CommonRPC {
|
|
|
168
173
|
if (this.closed) {
|
|
169
174
|
return
|
|
170
175
|
}
|
|
171
|
-
this.closed = true
|
|
172
|
-
// note:
|
|
173
|
-
|
|
174
|
-
await this.writeCallCancel()
|
|
175
|
-
}
|
|
176
|
+
this.closed = err ?? true
|
|
177
|
+
// note: this does nothing if _source is already ended.
|
|
178
|
+
await this.writeCallCancel()
|
|
176
179
|
this._source.end()
|
|
177
180
|
this._rpcDataSource.end(err)
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
// closeWrite closes the call for writing.
|
|
181
|
-
public closeWrite() {
|
|
182
|
-
this._source.end()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
183
|
// _createSink returns a value for the sink field.
|
|
186
184
|
private _createSink(): Sink<Source<Packet>> {
|
|
187
185
|
return async (source: Source<Packet>) => {
|
package/srpc/muxed-conn.go
CHANGED
|
@@ -72,7 +72,7 @@ func NewClientWithMuxedConn(conn network.MuxedConn) Client {
|
|
|
72
72
|
|
|
73
73
|
// NewOpenStreamWithMuxedConn constructs a OpenStream func with a MuxedConn.
|
|
74
74
|
func NewOpenStreamWithMuxedConn(conn network.MuxedConn) OpenStreamFunc {
|
|
75
|
-
return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (
|
|
75
|
+
return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (PacketWriter, error) {
|
|
76
76
|
mstrm, err := conn.OpenStream(ctx)
|
|
77
77
|
if err != nil {
|
|
78
78
|
return nil, err
|
package/srpc/packet-rw.go
CHANGED
package/srpc/packet.go
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package srpc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// CloseHandler handles the stream closing with an optional error.
|
|
4
|
+
type CloseHandler = func(closeErr error)
|
|
4
5
|
|
|
5
6
|
// PacketHandler handles a packet.
|
|
6
7
|
//
|
|
@@ -11,12 +12,6 @@ type PacketHandler = func(pkt *Packet) error
|
|
|
11
12
|
// PacketDataHandler handles a packet before it is parsed.
|
|
12
13
|
type PacketDataHandler = func(data []byte) error
|
|
13
14
|
|
|
14
|
-
// CloseHandler handles the stream closing with an optional error.
|
|
15
|
-
type CloseHandler = func(closeErr error)
|
|
16
|
-
|
|
17
|
-
// RawStreamCtor is a function that builds a raw stream.
|
|
18
|
-
type RawStreamCtor func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error)
|
|
19
|
-
|
|
20
15
|
// NewPacketDataHandler wraps a PacketHandler with a decoding step.
|
|
21
16
|
func NewPacketDataHandler(handler PacketHandler) PacketDataHandler {
|
|
22
17
|
return func(data []byte) error {
|
package/srpc/pushable.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { Pushable } from 'it-pushable'
|
|
|
2
2
|
import { Sink, Source } from 'it-stream-types'
|
|
3
3
|
|
|
4
4
|
// writeToPushable writes the incoming server data to the pushable.
|
|
5
|
+
//
|
|
6
|
+
// this will not throw an error: it instead ends out w/ the error.
|
|
5
7
|
export async function writeToPushable<T>(
|
|
6
8
|
dataSource: AsyncIterable<T>,
|
|
7
9
|
out: Pushable<T>,
|
|
@@ -13,7 +15,6 @@ export async function writeToPushable<T>(
|
|
|
13
15
|
out.end()
|
|
14
16
|
} catch (err) {
|
|
15
17
|
out.end(err as Error)
|
|
16
|
-
throw err
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
package/srpc/server-pipe.go
CHANGED
|
@@ -9,7 +9,7 @@ import (
|
|
|
9
9
|
// Stream with the given Server. Starts read pumps for both. Starts the
|
|
10
10
|
// HandleStream function on the server in a separate goroutine.
|
|
11
11
|
func NewServerPipe(server *Server) OpenStreamFunc {
|
|
12
|
-
return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (
|
|
12
|
+
return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (PacketWriter, error) {
|
|
13
13
|
srvPipe, clientPipe := net.Pipe()
|
|
14
14
|
go server.HandleStream(ctx, srvPipe)
|
|
15
15
|
clientPrw := NewPacketReadWriter(clientPipe)
|
package/srpc/server-rpc.go
CHANGED
|
@@ -15,7 +15,7 @@ type ServerRPC struct {
|
|
|
15
15
|
|
|
16
16
|
// NewServerRPC constructs a new ServerRPC session.
|
|
17
17
|
// note: call SetWriter before handling any incoming messages.
|
|
18
|
-
func NewServerRPC(ctx context.Context, invoker Invoker, writer
|
|
18
|
+
func NewServerRPC(ctx context.Context, invoker Invoker, writer PacketWriter) *ServerRPC {
|
|
19
19
|
rpc := &ServerRPC{invoker: invoker}
|
|
20
20
|
initCommonRPC(ctx, &rpc.commonRPC)
|
|
21
21
|
rpc.writer = writer
|
package/srpc/watchdog.ts
CHANGED
package/srpc/writer.go
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
package srpc
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
type
|
|
5
|
-
// Write writes raw data to the remote.
|
|
6
|
-
Write(p []byte) (n int, err error)
|
|
3
|
+
// PacketWriter is the interface used to write messages to a PacketStream.
|
|
4
|
+
type PacketWriter interface {
|
|
7
5
|
// WritePacket writes a packet to the remote.
|
|
8
6
|
WritePacket(p *Packet) error
|
|
9
7
|
// Close closes the writer.
|
package/srpc/raw-stream-rwc.go
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
package srpc
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"io"
|
|
6
|
-
"sync"
|
|
7
|
-
|
|
8
|
-
"github.com/aperturerobotics/util/broadcast"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// RawStreamRwc implements io.ReadWriteCloser with a raw stream.
|
|
12
|
-
type RawStreamRwc struct {
|
|
13
|
-
// writer is used to write messages to the remote.
|
|
14
|
-
writer Writer
|
|
15
|
-
// mtx guards below fields
|
|
16
|
-
mtx sync.Mutex
|
|
17
|
-
// bcast is broadcasted when data is added to readQueue
|
|
18
|
-
bcast broadcast.Broadcast
|
|
19
|
-
// readQueue holds incoming data to read
|
|
20
|
-
readQueue [][]byte
|
|
21
|
-
// closed indicates whether the stream is closed
|
|
22
|
-
closed bool
|
|
23
|
-
// closeErr stores the error, if any, when closing the stream
|
|
24
|
-
closeErr error
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
func NewRawStreamRwc(ctx context.Context, ctorFn RawStreamCtor) (*RawStreamRwc, error) {
|
|
28
|
-
rwc := &RawStreamRwc{}
|
|
29
|
-
var err error
|
|
30
|
-
rwc.writer, err = ctorFn(ctx, rwc.handlePacketData, rwc.handleClose)
|
|
31
|
-
if err != nil {
|
|
32
|
-
return nil, err
|
|
33
|
-
}
|
|
34
|
-
return rwc, nil
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// handlePacketData implements PacketDataHandler.
|
|
38
|
-
func (r *RawStreamRwc) handlePacketData(pkt []byte) error {
|
|
39
|
-
r.mtx.Lock()
|
|
40
|
-
defer r.mtx.Unlock()
|
|
41
|
-
|
|
42
|
-
if r.closed {
|
|
43
|
-
return io.ErrClosedPipe
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
r.readQueue = append(r.readQueue, pkt)
|
|
47
|
-
r.bcast.Broadcast()
|
|
48
|
-
return nil
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// handleClose handles the stream closing with an optional error.
|
|
52
|
-
func (r *RawStreamRwc) handleClose(closeErr error) {
|
|
53
|
-
r.mtx.Lock()
|
|
54
|
-
defer r.mtx.Unlock()
|
|
55
|
-
if r.closed {
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
r.closed = true
|
|
59
|
-
r.closeErr = closeErr
|
|
60
|
-
r.bcast.Broadcast()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Read reads data from the stream to p.
|
|
64
|
-
// Implements io.Reader.
|
|
65
|
-
func (r *RawStreamRwc) Read(p []byte) (n int, err error) {
|
|
66
|
-
readBuf := p
|
|
67
|
-
for len(readBuf) != 0 && err == nil {
|
|
68
|
-
// if the buffer has data, read from it.
|
|
69
|
-
var rn int
|
|
70
|
-
var read []byte
|
|
71
|
-
|
|
72
|
-
r.mtx.Lock()
|
|
73
|
-
if len(r.readQueue) != 0 {
|
|
74
|
-
nrq := r.readQueue[0]
|
|
75
|
-
// rn = amount of data to read
|
|
76
|
-
// minimum of len(readBuf) (length of space remaining in p) and len(nrq) (size of next pkt in read queue)
|
|
77
|
-
rn = min(len(readBuf), len(nrq))
|
|
78
|
-
// read the contents of nrq up to rn bytes
|
|
79
|
-
read = nrq[:rn]
|
|
80
|
-
// get the remainder of the packet that we won't read this time
|
|
81
|
-
nrq = nrq[rn:]
|
|
82
|
-
// if there is no more to read drop the pkt from the queue
|
|
83
|
-
if len(nrq) == 0 {
|
|
84
|
-
r.readQueue[0] = nil
|
|
85
|
-
r.readQueue = r.readQueue[1:]
|
|
86
|
-
} else {
|
|
87
|
-
// otherwise update the queued packet to be just the remainder
|
|
88
|
-
r.readQueue[0] = nrq
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// check if the stream is closed
|
|
93
|
-
closed, closedErr := r.closed, r.closeErr
|
|
94
|
-
var wait <-chan struct{}
|
|
95
|
-
|
|
96
|
-
// if we didn't read anything and !closed, wait till something changes.
|
|
97
|
-
if rn == 0 && !closed {
|
|
98
|
-
wait = r.bcast.GetWaitCh()
|
|
99
|
-
}
|
|
100
|
-
r.mtx.Unlock()
|
|
101
|
-
|
|
102
|
-
// if we read data, copy it to the output buf
|
|
103
|
-
if rn != 0 {
|
|
104
|
-
// copy data to output buf
|
|
105
|
-
copy(readBuf, read)
|
|
106
|
-
n += rn
|
|
107
|
-
// advance readBuf by rn
|
|
108
|
-
readBuf = readBuf[rn:]
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// if we read data to p already, return now.
|
|
113
|
-
if n != 0 {
|
|
114
|
-
break
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// if closed or error, return.
|
|
118
|
-
if closed {
|
|
119
|
-
if closedErr != nil {
|
|
120
|
-
return n, closedErr
|
|
121
|
-
}
|
|
122
|
-
return n, io.EOF
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// wait for data or closed
|
|
126
|
-
<-wait
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return n, err
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Write writes data to the stream.
|
|
133
|
-
func (r *RawStreamRwc) Write(p []byte) (int, error) {
|
|
134
|
-
return r.writer.Write(p)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// WritePacket writes a packet to the remote.
|
|
138
|
-
func (r *RawStreamRwc) WritePacket(p *Packet) error {
|
|
139
|
-
return r.writer.WritePacket(p)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Close closes the stream.
|
|
143
|
-
func (r *RawStreamRwc) Close() error {
|
|
144
|
-
r.mtx.Lock()
|
|
145
|
-
defer r.mtx.Unlock()
|
|
146
|
-
|
|
147
|
-
if r.closed {
|
|
148
|
-
return r.closeErr
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
r.closed = true
|
|
152
|
-
r.closeErr = r.writer.Close()
|
|
153
|
-
r.bcast.Broadcast()
|
|
154
|
-
return r.closeErr
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// _ is a type assertion
|
|
158
|
-
var (
|
|
159
|
-
_ io.ReadWriteCloser = ((*RawStreamRwc)(nil))
|
|
160
|
-
_ Writer = ((*RawStreamRwc)(nil))
|
|
161
|
-
)
|