bare-rpc 1.3.6 → 1.3.8
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/index.js +57 -9
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +4 -0
- package/lib/outgoing-request.js +6 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const safetyCatch = require('safety-catch')
|
|
|
2
2
|
const b4a = require('b4a')
|
|
3
3
|
const c = require('compact-encoding')
|
|
4
4
|
const m = require('./lib/messages')
|
|
5
|
+
const errors = require('./lib/errors')
|
|
5
6
|
const { type: t, stream: s } = require('./lib/constants')
|
|
6
7
|
const IncomingEvent = require('./lib/incoming-event')
|
|
7
8
|
const IncomingRequest = require('./lib/incoming-request')
|
|
@@ -28,6 +29,9 @@ module.exports = exports = class RPC {
|
|
|
28
29
|
this._frame = -1
|
|
29
30
|
this._draining = []
|
|
30
31
|
|
|
32
|
+
this._closed = false
|
|
33
|
+
this._error = null
|
|
34
|
+
|
|
31
35
|
if (typeof onrequest === 'function') {
|
|
32
36
|
onrequest = onrequest.bind(this)
|
|
33
37
|
} else {
|
|
@@ -36,10 +40,15 @@ module.exports = exports = class RPC {
|
|
|
36
40
|
|
|
37
41
|
this._onrequest = onrequest
|
|
38
42
|
this._onerror = this._onerror.bind(this)
|
|
43
|
+
this._onclose = this._onclose.bind(this)
|
|
39
44
|
this._ondata = this._ondata.bind(this)
|
|
40
45
|
this._ondrain = this._ondrain.bind(this)
|
|
41
46
|
|
|
42
|
-
this._stream
|
|
47
|
+
this._stream
|
|
48
|
+
.on('error', this._onerror)
|
|
49
|
+
.on('close', this._onclose)
|
|
50
|
+
.on('data', this._ondata)
|
|
51
|
+
.on('drain', this._ondrain)
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
// Whether there are no requests or responses currently in flight. Useful for
|
|
@@ -95,6 +104,11 @@ module.exports = exports = class RPC {
|
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
_sendRequest(request, data = null) {
|
|
107
|
+
// Once the channel has been torn down there is nowhere left to send the
|
|
108
|
+
// request. Skip registering it so that it never leaks into the maps; the
|
|
109
|
+
// awaiting reply is rejected through `RPCOutgoingRequest.reply()` instead.
|
|
110
|
+
if (this._closed) return
|
|
111
|
+
|
|
98
112
|
this._outgoingRequests.set(request.id, request)
|
|
99
113
|
|
|
100
114
|
this._sendMessage({
|
|
@@ -108,21 +122,33 @@ module.exports = exports = class RPC {
|
|
|
108
122
|
|
|
109
123
|
_createRequestStream(request, isInitiator, opts) {
|
|
110
124
|
if (isInitiator) {
|
|
111
|
-
this._outgoingRequests.set(request.id, request)
|
|
112
|
-
|
|
113
125
|
request._requestStream = new OutgoingStream(this, request, t.REQUEST, opts)
|
|
114
126
|
|
|
127
|
+
// Once the channel has been torn down there is nowhere left to open the
|
|
128
|
+
// stream, so destroy it right away rather than leave it hanging.
|
|
129
|
+
if (this._closed) return request._requestStream.destroy(this._error)
|
|
130
|
+
|
|
131
|
+
this._outgoingRequests.set(request.id, request)
|
|
132
|
+
|
|
115
133
|
request._requestStream.on('close', () => this._gcOutgoingRequest(request))
|
|
116
134
|
} else {
|
|
117
|
-
this._incomingRequests.set(request.id, request)
|
|
118
|
-
|
|
119
135
|
request._requestStream = new IncomingStream(this, request, t.REQUEST, opts)
|
|
120
136
|
|
|
137
|
+
if (this._closed) return request._requestStream.destroy(this._error)
|
|
138
|
+
|
|
139
|
+
this._incomingRequests.set(request.id, request)
|
|
140
|
+
|
|
121
141
|
request._requestStream.on('close', () => this._incomingRequests.delete(request.id))
|
|
122
142
|
}
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
_sendResponse(request, data) {
|
|
146
|
+
// Once the channel has been torn down there is nowhere left to send the
|
|
147
|
+
// response, so drop it rather than encode and write a message that would
|
|
148
|
+
// only be discarded. The reply is fire-and-forget, so there is nothing to
|
|
149
|
+
// settle.
|
|
150
|
+
if (this._closed) return
|
|
151
|
+
|
|
126
152
|
this._sendMessage({
|
|
127
153
|
type: t.RESPONSE,
|
|
128
154
|
id: request.id,
|
|
@@ -134,16 +160,22 @@ module.exports = exports = class RPC {
|
|
|
134
160
|
|
|
135
161
|
_createResponseStream(request, isInitiator, opts) {
|
|
136
162
|
if (isInitiator) {
|
|
137
|
-
this._outgoingResponses.set(request.id, request)
|
|
138
|
-
|
|
139
163
|
request._responseStream = new OutgoingStream(this, request, t.RESPONSE, opts)
|
|
140
164
|
|
|
165
|
+
// Once the channel has been torn down there is nowhere left to open the
|
|
166
|
+
// stream, so destroy it right away rather than leave it hanging.
|
|
167
|
+
if (this._closed) return request._responseStream.destroy(this._error)
|
|
168
|
+
|
|
169
|
+
this._outgoingResponses.set(request.id, request)
|
|
170
|
+
|
|
141
171
|
request._responseStream.on('close', () => this._outgoingResponses.delete(request.id))
|
|
142
172
|
} else {
|
|
143
|
-
this._incomingResponses.set(request.id, request)
|
|
144
|
-
|
|
145
173
|
request._responseStream = new IncomingStream(this, request, t.RESPONSE, opts)
|
|
146
174
|
|
|
175
|
+
if (this._closed) return request._responseStream.destroy(this._error)
|
|
176
|
+
|
|
177
|
+
this._incomingResponses.set(request.id, request)
|
|
178
|
+
|
|
147
179
|
request._responseStream.on('close', () => {
|
|
148
180
|
this._incomingResponses.delete(request.id)
|
|
149
181
|
|
|
@@ -175,6 +207,22 @@ module.exports = exports = class RPC {
|
|
|
175
207
|
}
|
|
176
208
|
|
|
177
209
|
_onerror(err) {
|
|
210
|
+
this._teardown(err)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_onclose() {
|
|
214
|
+
// A clean close of the underlying stream leaves in-flight requests and
|
|
215
|
+
// responses with nowhere to complete, so tear them down as if the channel
|
|
216
|
+
// had errored.
|
|
217
|
+
this._teardown(errors.CHANNEL_CLOSED('Channel closed'))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_teardown(err) {
|
|
221
|
+
// Mark the channel as closed so that any calls made from here on are
|
|
222
|
+
// rejected or destroyed immediately rather than left hanging.
|
|
223
|
+
this._closed = true
|
|
224
|
+
this._error = err
|
|
225
|
+
|
|
178
226
|
this._ondrain(err)
|
|
179
227
|
|
|
180
228
|
// Reject any request awaiting a plain reply. Requests whose response is
|
package/lib/errors.d.ts
CHANGED
package/lib/errors.js
CHANGED
|
@@ -23,4 +23,8 @@ module.exports = class RPCError extends Error {
|
|
|
23
23
|
static ALREADY_RECEIVED(msg) {
|
|
24
24
|
return new RPCError(msg, 'ALREADY_RECEIVED', RPCError.ALREADY_RECEIVED)
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
static CHANNEL_CLOSED(msg) {
|
|
28
|
+
return new RPCError(msg, 'CHANNEL_CLOSED', RPCError.CHANNEL_CLOSED)
|
|
29
|
+
}
|
|
26
30
|
}
|
package/lib/outgoing-request.js
CHANGED
|
@@ -46,6 +46,12 @@ module.exports = class RPCOutgoingRequest {
|
|
|
46
46
|
|
|
47
47
|
this.received = true
|
|
48
48
|
|
|
49
|
+
// If the channel was torn down before a response arrived there is nothing
|
|
50
|
+
// left to await, so reject with the teardown error rather than hang. This
|
|
51
|
+
// covers both requests sent after teardown and requests still awaiting a
|
|
52
|
+
// reply when teardown happens.
|
|
53
|
+
if (this.rpc._closed && !this._responded) this._reject(this.rpc._error)
|
|
54
|
+
|
|
49
55
|
return encoding ? this._promise.then((data) => c.decode(encoding, data)) : this._promise
|
|
50
56
|
}
|
|
51
57
|
|