Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit 9db17eb

Browse files
jacobheunvasco-santos
authored andcommitted
fix: random walk (#104)
1 parent f03619e commit 9db17eb

File tree

7 files changed

+337
-72
lines changed

7 files changed

+337
-72
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
},
3838
"homepage": "https://github.com/libp2p/js-libp2p-kad-dht",
3939
"dependencies": {
40+
"abort-controller": "^3.0.0",
4041
"async": "^2.6.2",
4142
"base32.js": "~0.1.0",
4243
"chai-checkmark": "^1.0.1",

src/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ exports.defaultRandomWalk = {
4444
enabled: true,
4545
queriesPerPeriod: 1,
4646
interval: 5 * minute,
47-
timeout: 10 * second,
47+
timeout: 30 * second,
4848
delay: 10 * second
4949
}

src/index.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class KadDHT extends EventEmitter {
3838
* @property {boolean} enabled discovery enabled (default: true)
3939
* @property {number} queriesPerPeriod how many queries to run per period (default: 1)
4040
* @property {number} interval how often to run the the random-walk process, in milliseconds (default: 300000)
41-
* @property {number} timeout how long to wait for the the random-walk query to run, in milliseconds (default: 10000)
41+
* @property {number} timeout how long to wait for the the random-walk query to run, in milliseconds (default: 30000)
4242
* @property {number} delay how long to wait before starting the first random walk, in milliseconds (default: 10000)
4343
*/
4444

@@ -173,11 +173,10 @@ class KadDHT extends EventEmitter {
173173
*/
174174
stop (callback) {
175175
this._running = false
176-
this.randomWalk.stop(() => { // guarantee that random walk is stopped if it was started
177-
this.providers.stop()
178-
this.network.stop(callback)
179-
})
176+
this.randomWalk.stop()
177+
this.providers.stop()
180178
this._queryManager.stop()
179+
this.network.stop(callback)
181180
}
182181

183182
/**

src/random-walk.js

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
const times = require('async/times')
44
const crypto = require('libp2p-crypto')
55
const waterfall = require('async/waterfall')
6-
const timeout = require('async/timeout')
76
const multihashing = require('multihashing-async')
87
const PeerId = require('peer-id')
98
const assert = require('assert')
109
const c = require('./constants')
1110
const { logger } = require('./utils')
11+
const AbortController = require('abort-controller')
1212

1313
const errcode = require('err-code')
1414

@@ -25,9 +25,8 @@ class RandomWalk {
2525
* @param {DHT} options.dht
2626
*/
2727
constructor (dht, options) {
28-
this._options = { ...c.defaultRandomWalk, ...options }
2928
assert(dht, 'Random Walk needs an instance of the Kademlia DHT')
30-
this._runningHandle = null
29+
this._options = { ...c.defaultRandomWalk, ...options }
3130
this._kadDHT = dht
3231
this.log = logger(dht.peerInfo.id, 'random-walk')
3332
}
@@ -41,64 +40,44 @@ class RandomWalk {
4140
*/
4241
start () {
4342
// Don't run twice
44-
if (this._running || !this._options.enabled) { return }
45-
46-
// Create running handle
47-
const runningHandle = {
48-
_onCancel: null,
49-
_timeoutId: null,
50-
runPeriodically: (walk, period) => {
51-
runningHandle._timeoutId = setTimeout(() => {
52-
runningHandle._timeoutId = null
53-
54-
walk((nextPeriod) => {
55-
// Was walk cancelled while fn was being called?
56-
if (runningHandle._onCancel) {
57-
return runningHandle._onCancel()
58-
}
59-
// Schedule next
60-
runningHandle.runPeriodically(walk, nextPeriod)
61-
})
62-
}, period)
63-
},
64-
cancel: (cb) => {
65-
// Not currently running, can callback immediately
66-
if (runningHandle._timeoutId) {
67-
clearTimeout(runningHandle._timeoutId)
68-
return cb()
69-
}
70-
// Wait to finish and then call callback
71-
runningHandle._onCancel = cb
72-
}
73-
}
43+
if (this._timeoutId || !this._options.enabled) { return }
7444

7545
// Start doing random walks after `this._options.delay`
76-
runningHandle._timeoutId = setTimeout(() => {
46+
this._timeoutId = setTimeout(() => {
7747
// Start runner immediately
78-
runningHandle.runPeriodically((done) => {
48+
this._runPeriodically((done) => {
7949
// Each subsequent walk should run on a `this._options.interval` interval
8050
this._walk(this._options.queriesPerPeriod, this._options.timeout, () => done(this._options.interval))
8151
}, 0)
8252
}, this._options.delay)
83-
84-
this._runningHandle = runningHandle
8553
}
8654

8755
/**
88-
* Stop the random-walk process.
89-
* @param {function(Error)} callback
56+
* Stop the random-walk process. Any active
57+
* queries will be aborted.
9058
*
9159
* @returns {void}
9260
*/
93-
stop (callback) {
94-
const runningHandle = this._runningHandle
95-
96-
if (!runningHandle) {
97-
return callback()
98-
}
61+
stop () {
62+
clearTimeout(this._timeoutId)
63+
this._timeoutId = null
64+
this._controller && this._controller.abort()
65+
}
9966

100-
this._runningHandle = null
101-
runningHandle.cancel(callback)
67+
/**
68+
* Run function `walk` on every `interval` ms
69+
* @param {function(callback)} walk The function to execute on `interval`
70+
* @param {number} interval The interval to run on in ms
71+
*
72+
* @private
73+
*/
74+
_runPeriodically (walk, interval) {
75+
this._timeoutId = setTimeout(() => {
76+
walk((nextInterval) => {
77+
// Schedule next
78+
this._runPeriodically(walk, nextInterval)
79+
})
80+
}, interval)
10281
}
10382

10483
/**
@@ -113,39 +92,63 @@ class RandomWalk {
11392
*/
11493
_walk (queries, walkTimeout, callback) {
11594
this.log('start')
95+
this._controller = new AbortController()
11696

117-
times(queries, (i, cb) => {
97+
times(queries, (i, next) => {
98+
this.log('running query %d', i)
99+
100+
// Perform the walk
118101
waterfall([
119102
(cb) => this._randomPeerId(cb),
120-
(id, cb) => timeout((cb) => {
121-
this._query(id, cb)
122-
}, walkTimeout)(cb)
103+
(id, cb) => {
104+
// Check if we've happened to already abort
105+
if (!this._controller) return cb()
106+
107+
this._query(id, {
108+
timeout: walkTimeout,
109+
signal: this._controller.signal
110+
}, cb)
111+
}
123112
], (err) => {
124-
if (err) {
125-
this.log.error('query finished with error', err)
126-
return callback(err)
113+
if (err && err.code !== 'ETIMEDOUT') {
114+
this.log.error('query %d finished with error', i, err)
115+
return next(err)
127116
}
128117

129-
this.log('done')
130-
callback(null)
118+
this.log('finished query %d', i)
119+
next(null)
131120
})
121+
}, (err) => {
122+
this._controller = null
123+
this.log('finished queries')
124+
callback(err)
132125
})
133126
}
134127

135128
/**
136129
* The query run during a random walk request.
137130
*
131+
* TODO: While query currently supports an abort controller, it is not
132+
* yet supported by `DHT.findPeer`. Once https://github.com/libp2p/js-libp2p-kad-dht/pull/82
133+
* is complete, and AbortController support has been added to the
134+
* DHT query functions, the abort here will just work, provided the
135+
* functions support `options.signal`. Once done, this todo should be
136+
* removed.
137+
*
138138
* @param {PeerId} id
139+
* @param {object} options
140+
* @param {number} options.timeout
141+
* @param {AbortControllerSignal} options.signal
139142
* @param {function(Error)} callback
140143
* @returns {void}
141144
*
142145
* @private
143146
*/
144-
_query (id, callback) {
147+
_query (id, options, callback) {
145148
this.log('query:%s', id.toB58String())
146149

147-
this._kadDHT.findPeer(id, (err, peer) => {
148-
if (err.code === 'ERR_NOT_FOUND') {
150+
this._kadDHT.findPeer(id, options, (err, peer) => {
151+
if (err && err.code === 'ERR_NOT_FOUND') {
149152
// expected case, we asked for random stuff after all
150153
return callback()
151154
}

test/kad-dht.spec.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function connect (a, b, callback) {
7373

7474
function bootstrap (dhts) {
7575
dhts.forEach((dht) => {
76-
dht.randomWalk._walk(1, 1000, () => {})
76+
dht.randomWalk._walk(1, 10000, () => {})
7777
})
7878
}
7979

@@ -574,17 +574,13 @@ describe('KadDHT', () => {
574574
})
575575

576576
it('random-walk', function (done) {
577-
this.timeout(10 * 1000)
577+
this.timeout(20 * 1000)
578578

579579
const nDHTs = 20
580580
const tdht = new TestDHT()
581581

582582
// random walk disabled for a manual usage
583-
tdht.spawn(nDHTs, {
584-
randomWalk: {
585-
enabled: false
586-
}
587-
}, (err, dhts) => {
583+
tdht.spawn(nDHTs, (err, dhts) => {
588584
expect(err).to.not.exist()
589585

590586
series([

0 commit comments

Comments
 (0)