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 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.on('error', this._onerror).on('data', this._ondata).on('drain', this._ondrain)
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
@@ -1,6 +1,8 @@
1
1
  declare class RPCError extends Error {
2
2
  static UNKNOWN_MESSAGE(msg: string): RPCError
3
3
  static ALREADY_SENT(msg: string): RPCError
4
+ static ALREADY_RECEIVED(msg: string): RPCError
5
+ static CHANNEL_CLOSED(msg: string): RPCError
4
6
  }
5
7
 
6
8
  export = RPCError
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
  }
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-rpc",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "librpc ABI compatible RPC for Bare",
5
5
  "exports": {
6
6
  ".": {