bare-rpc 1.3.4 → 1.3.6

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.d.ts CHANGED
@@ -95,6 +95,8 @@ declare class RPCOutgoingStream extends Writable {
95
95
  }
96
96
 
97
97
  interface RPC {
98
+ readonly idle: boolean
99
+
98
100
  event(command: number): RPCOutgoingEvent
99
101
  request(command: number): RPCOutgoingRequest
100
102
  }
package/index.js CHANGED
@@ -42,6 +42,19 @@ module.exports = exports = class RPC {
42
42
  this._stream.on('error', this._onerror).on('data', this._ondata).on('drain', this._ondrain)
43
43
  }
44
44
 
45
+ // Whether there are no requests or responses currently in flight. Useful for
46
+ // determining when it is safe to tear down the underlying stream.
47
+ get idle() {
48
+ return (
49
+ this._outgoingRequests.size === 0 &&
50
+ this._outgoingResponses.size === 0 &&
51
+ this._incomingRequests.size === 0 &&
52
+ this._incomingResponses.size === 0 &&
53
+ this._pendingRequests.size === 0 &&
54
+ this._pendingResponses.size === 0
55
+ )
56
+ }
57
+
45
58
  event(command) {
46
59
  return new OutgoingEvent(this, command)
47
60
  }
@@ -51,6 +64,14 @@ module.exports = exports = class RPC {
51
64
  }
52
65
 
53
66
  _sendMessage(message, cb) {
67
+ // Once the underlying stream is being torn down there is nowhere left to
68
+ // write, so drop the message and settle the callback immediately. This
69
+ // keeps stream teardown from stalling on a drain that will never arrive.
70
+ if (this._stream.destroying) {
71
+ if (cb) cb(null)
72
+ return
73
+ }
74
+
54
75
  const header = c.encode(m.header, message)
55
76
 
56
77
  let flushed = this._stream.write(header)
@@ -90,6 +111,8 @@ module.exports = exports = class RPC {
90
111
  this._outgoingRequests.set(request.id, request)
91
112
 
92
113
  request._requestStream = new OutgoingStream(this, request, t.REQUEST, opts)
114
+
115
+ request._requestStream.on('close', () => this._gcOutgoingRequest(request))
93
116
  } else {
94
117
  this._incomingRequests.set(request.id, request)
95
118
 
@@ -114,12 +137,19 @@ module.exports = exports = class RPC {
114
137
  this._outgoingResponses.set(request.id, request)
115
138
 
116
139
  request._responseStream = new OutgoingStream(this, request, t.RESPONSE, opts)
140
+
141
+ request._responseStream.on('close', () => this._outgoingResponses.delete(request.id))
117
142
  } else {
118
143
  this._incomingResponses.set(request.id, request)
119
144
 
120
145
  request._responseStream = new IncomingStream(this, request, t.RESPONSE, opts)
121
146
 
122
- request._responseStream.on('close', () => this._incomingResponses.delete(request.id))
147
+ request._responseStream.on('close', () => {
148
+ this._incomingResponses.delete(request.id)
149
+
150
+ request._responded = true
151
+ this._gcOutgoingRequest(request)
152
+ })
123
153
  }
124
154
  }
125
155
 
@@ -133,10 +163,54 @@ module.exports = exports = class RPC {
133
163
  })
134
164
  }
135
165
 
166
+ _gcOutgoingRequest(request) {
167
+ // The entry must stay reachable for as long as an incoming message might
168
+ // still target it; until the response has settled and, if the request was
169
+ // streamed, until the outgoing request stream has closed. Either can be the
170
+ // last to arrive, so only remove the entry once both have completed.
171
+ if (!request._responded) return
172
+ if (request._requestStream !== null && !request._requestStream.destroyed) return
173
+
174
+ this._outgoingRequests.delete(request.id)
175
+ }
176
+
136
177
  _onerror(err) {
137
178
  this._ondrain(err)
138
179
 
139
- // TODO Destroy pending requests and responses
180
+ // Reject any request awaiting a plain reply. Requests whose response is
181
+ // delivered over a stream are torn down through that stream below.
182
+ for (const request of this._outgoingRequests.values()) {
183
+ if (request.received && request._responseStream === null) request._reject(err)
184
+ }
185
+
186
+ // Collect the streams first, deduplicating as a single request may appear
187
+ // in more than one map, then destroy them once the maps have been cleared.
188
+ const streams = new Set()
189
+
190
+ for (const request of this._outgoingRequests.values()) {
191
+ if (request._requestStream !== null) streams.add(request._requestStream)
192
+ }
193
+
194
+ for (const request of this._incomingRequests.values()) {
195
+ if (request._requestStream !== null) streams.add(request._requestStream)
196
+ }
197
+
198
+ for (const request of this._outgoingResponses.values()) {
199
+ if (request._responseStream !== null) streams.add(request._responseStream)
200
+ }
201
+
202
+ for (const request of this._incomingResponses.values()) {
203
+ if (request._responseStream !== null) streams.add(request._responseStream)
204
+ }
205
+
206
+ this._outgoingRequests.clear()
207
+ this._outgoingResponses.clear()
208
+ this._incomingRequests.clear()
209
+ this._incomingResponses.clear()
210
+ this._pendingRequests.clear()
211
+ this._pendingResponses.clear()
212
+
213
+ for (const stream of streams) stream.destroy(err)
140
214
  }
141
215
 
142
216
  _ondata(data) {
@@ -226,8 +300,14 @@ module.exports = exports = class RPC {
226
300
  if (request === undefined) return
227
301
 
228
302
  if (message.error) {
303
+ request._responded = true
304
+ this._gcOutgoingRequest(request)
305
+
229
306
  request._reject(message.error)
230
307
  } else if (message.stream === 0) {
308
+ request._responded = true
309
+ this._gcOutgoingRequest(request)
310
+
231
311
  request._resolve(message.data)
232
312
  }
233
313
  }
@@ -9,6 +9,8 @@ module.exports = class RPCOutgoingRequest {
9
9
  this.sent = false
10
10
  this.received = false
11
11
 
12
+ this._responded = false
13
+
12
14
  this._promise = new Promise((resolve, reject) => {
13
15
  this._resolve = resolve
14
16
  this._reject = reject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-rpc",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "librpc ABI compatible RPC for Bare",
5
5
  "exports": {
6
6
  ".": {