Skip to content

Commit 633b0c2

Browse files
committed
fix: clean up pending dials abort per feedback
1 parent 7d50549 commit 633b0c2

File tree

4 files changed

+66
-17
lines changed

4 files changed

+66
-17
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"dependencies": {
4444
"abort-controller": "^3.0.0",
4545
"aggregate-error": "^3.0.1",
46-
"any-signal": "^1.0.0",
46+
"any-signal": "^1.1.0",
4747
"async": "^2.6.2",
4848
"async-iterator-all": "^1.0.0",
4949
"bignumber.js": "^9.0.0",
@@ -78,6 +78,7 @@
7878
"pull-handshake": "^1.1.4",
7979
"pull-stream": "^3.6.9",
8080
"retimer": "^2.0.0",
81+
"timeout-abort-controller": "^1.0.0",
8182
"xsalsa20": "^1.0.2"
8283
},
8384
"devDependencies": {

src/dialer/index.js

+20-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const multiaddr = require('multiaddr')
44
const errCode = require('err-code')
5-
const AbortController = require('abort-controller')
5+
const TimeoutController = require('timeout-abort-controller')
66
const anySignal = require('any-signal')
77
const debug = require('debug')
88
const log = debug('libp2p:dialer')
@@ -38,7 +38,21 @@ class Dialer {
3838
this.timeout = timeout
3939
this.perPeerLimit = perPeerLimit
4040
this.tokens = [...new Array(concurrency)].map((_, index) => index)
41-
this.pendingDials = new Set()
41+
this._pendingDials = new Set()
42+
}
43+
44+
/**
45+
* Clears any pending dials
46+
*/
47+
destroy () {
48+
for (const dial of this._pendingDials.values()) {
49+
try {
50+
dial.controller.abort()
51+
} catch (err) {
52+
log.error(err)
53+
}
54+
}
55+
this._pendingDials.clear()
4256
}
4357

4458
/**
@@ -64,21 +78,20 @@ class Dialer {
6478
})
6579

6680
// Combine the timeout signal and options.signal, if provided
67-
const timeoutController = new AbortController()
81+
const timeoutController = new TimeoutController(this.timeout)
6882
const signals = [timeoutController.signal]
6983
options.signal && signals.push(options.signal)
7084
const signal = anySignal(signals)
71-
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeout)
7285

7386
const dial = {
7487
dialRequest,
7588
controller: timeoutController
7689
}
77-
this.pendingDials.add(dial)
90+
this._pendingDials.add(dial)
7891

7992
try {
8093
const dialResult = await dialRequest.run({ ...options, signal })
81-
clearTimeout(timeoutId)
94+
timeoutController.clear()
8295
log('dial succeeded to %s', dialResult.remoteAddr)
8396
return dialResult
8497
} catch (err) {
@@ -89,7 +102,7 @@ class Dialer {
89102
log.error(err)
90103
throw err
91104
} finally {
92-
this.pendingDials.delete(dial)
105+
this._pendingDials.delete(dial)
93106
}
94107
}
95108

src/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,7 @@ class Libp2p extends EventEmitter {
199199
this._dht && this._dht.stop()
200200
])
201201

202-
for (const dial of this.dialer.pendingDials.values()) {
203-
dial.abort()
204-
}
202+
this.dialer.destroy()
205203

206204
await this.transportManager.close()
207205
await this.registrar.close()

test/dialing/direct.spec.js

+43-6
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ describe('Dialing (direct, WebSockets)', () => {
169169

170170
// We should have 2 in progress, and 1 waiting
171171
expect(dialer.tokens).to.have.length(0)
172-
expect(dialer.pendingDials.size).to.equal(1) // 1 dial request
172+
expect(dialer._pendingDials.size).to.equal(1) // 1 dial request
173173

174174
deferredDial.resolve(await createMockConnection())
175175

@@ -179,7 +179,45 @@ describe('Dialing (direct, WebSockets)', () => {
179179
// Only two dials will be run, as the first two succeeded
180180
expect(localTM.dial.callCount).to.equal(2)
181181
expect(dialer.tokens).to.have.length(2)
182-
expect(dialer.pendingDials.size).to.equal(0)
182+
expect(dialer._pendingDials.size).to.equal(0)
183+
})
184+
185+
it('.destroy should abort pending dials', async () => {
186+
const dialer = new Dialer({
187+
transportManager: localTM,
188+
concurrency: 2
189+
})
190+
191+
expect(dialer.tokens).to.have.length(2)
192+
193+
sinon.stub(localTM, 'dial').callsFake((_, options) => {
194+
const deferredDial = pDefer()
195+
const onAbort = () => {
196+
options.signal.removeEventListener('abort', onAbort)
197+
deferredDial.reject(new AbortError())
198+
}
199+
options.signal.addEventListener('abort', onAbort)
200+
return deferredDial.promise
201+
})
202+
203+
// Perform 3 multiaddr dials
204+
const dialPromise = dialer.connectToMultiaddr([remoteAddr, remoteAddr, remoteAddr])
205+
206+
// Let the call stack run
207+
await delay(0)
208+
209+
// We should have 2 in progress, and 1 waiting
210+
expect(dialer.tokens).to.have.length(0)
211+
expect(dialer._pendingDials.size).to.equal(1) // 1 dial request
212+
213+
try {
214+
dialer.destroy()
215+
await dialPromise
216+
expect.fail('should have failed')
217+
} catch (err) {
218+
expect(err).to.be.an.instanceof(AggregateError)
219+
expect(dialer._pendingDials.size).to.equal(0) // 1 dial request
220+
}
183221
})
184222

185223
describe('libp2p.dialer', () => {
@@ -290,13 +328,12 @@ describe('Dialing (direct, WebSockets)', () => {
290328
connEncryption: [Crypto]
291329
}
292330
})
293-
const abort = sinon.stub()
294-
const dials = [{ abort }, { abort }, { abort }]
295-
sinon.stub(libp2p.dialer, 'pendingDials').value(new Set(dials))
331+
332+
sinon.spy(libp2p.dialer, 'destroy')
296333

297334
await libp2p.stop()
298335

299-
expect(abort).to.have.property('callCount', 3)
336+
expect(libp2p.dialer.destroy).to.have.property('callCount', 1)
300337
})
301338
})
302339
})

0 commit comments

Comments
 (0)