@@ -4,10 +4,12 @@ import { anySignal } from 'any-signal'
4
4
import length from 'it-length'
5
5
import { pipe } from 'it-pipe'
6
6
import take from 'it-take'
7
+ import pDefer from 'p-defer'
8
+ import { pEvent } from 'p-event'
7
9
import { QUERY_SELF_INTERVAL , QUERY_SELF_TIMEOUT , K , QUERY_SELF_INITIAL_INTERVAL } from './constants.js'
8
- import type { KadDHTComponents } from './index.js'
9
10
import type { PeerRouting } from './peer-routing/index.js'
10
11
import type { RoutingTable } from './routing-table/index.js'
12
+ import type { PeerId } from '@libp2p/interface-peer-id'
11
13
import type { Startable } from '@libp2p/interfaces/startable'
12
14
import type { DeferredPromise } from 'p-defer'
13
15
@@ -22,44 +24,33 @@ export interface QuerySelfInit {
22
24
initialQuerySelfHasRun : DeferredPromise < void >
23
25
}
24
26
25
- function debounce ( func : ( ) => void , wait : number ) : ( ) => void {
26
- let timeout : ReturnType < typeof setTimeout > | undefined
27
-
28
- return function ( ) {
29
- const later = function ( ) : void {
30
- timeout = undefined
31
- func ( )
32
- }
33
-
34
- clearTimeout ( timeout )
35
- timeout = setTimeout ( later , wait )
36
- }
27
+ export interface QuerySelfComponents {
28
+ peerId : PeerId
37
29
}
38
30
39
31
/**
40
32
* Receives notifications of new peers joining the network that support the DHT protocol
41
33
*/
42
34
export class QuerySelf implements Startable {
43
35
private readonly log : Logger
44
- private readonly components : KadDHTComponents
36
+ private readonly components : QuerySelfComponents
45
37
private readonly peerRouting : PeerRouting
46
38
private readonly routingTable : RoutingTable
47
39
private readonly count : number
48
40
private readonly interval : number
49
41
private readonly initialInterval : number
50
42
private readonly queryTimeout : number
51
43
private started : boolean
52
- private running : boolean
53
44
private timeoutId ?: NodeJS . Timer
54
45
private controller ?: AbortController
55
46
private initialQuerySelfHasRun ?: DeferredPromise < void >
47
+ private querySelfPromise ?: DeferredPromise < void >
56
48
57
- constructor ( components : KadDHTComponents , init : QuerySelfInit ) {
49
+ constructor ( components : QuerySelfComponents , init : QuerySelfInit ) {
58
50
const { peerRouting, lan, count, interval, queryTimeout, routingTable } = init
59
51
60
52
this . components = components
61
53
this . log = logger ( `libp2p:kad-dht:${ lan ? 'lan' : 'wan' } :query-self` )
62
- this . running = false
63
54
this . started = false
64
55
this . peerRouting = peerRouting
65
56
this . routingTable = routingTable
@@ -68,25 +59,28 @@ export class QuerySelf implements Startable {
68
59
this . initialInterval = init . initialInterval ?? QUERY_SELF_INITIAL_INTERVAL
69
60
this . queryTimeout = queryTimeout ?? QUERY_SELF_TIMEOUT
70
61
this . initialQuerySelfHasRun = init . initialQuerySelfHasRun
71
-
72
- this . querySelf = debounce ( this . querySelf . bind ( this ) , 100 )
73
62
}
74
63
75
64
isStarted ( ) : boolean {
76
65
return this . started
77
66
}
78
67
79
- async start ( ) : Promise < void > {
68
+ start ( ) : void {
80
69
if ( this . started ) {
81
70
return
82
71
}
83
72
84
73
this . started = true
85
74
clearTimeout ( this . timeoutId )
86
- this . timeoutId = setTimeout ( this . querySelf . bind ( this ) , this . initialInterval )
75
+ this . timeoutId = setTimeout ( ( ) => {
76
+ this . querySelf ( )
77
+ . catch ( err => {
78
+ this . log . error ( 'error running self-query' , err )
79
+ } )
80
+ } , this . initialInterval )
87
81
}
88
82
89
- async stop ( ) : Promise < void > {
83
+ stop ( ) : void {
90
84
this . started = false
91
85
92
86
if ( this . timeoutId != null ) {
@@ -98,84 +92,68 @@ export class QuerySelf implements Startable {
98
92
}
99
93
}
100
94
101
- querySelf ( ) : void {
95
+ async querySelf ( ) : Promise < void > {
102
96
if ( ! this . started ) {
103
97
this . log ( 'skip self-query because we are not started' )
104
98
return
105
99
}
106
100
107
- if ( this . running ) {
108
- this . log ( 'skip self-query because we are already running, will run again in %dms' , this . interval )
109
- return
101
+ if ( this . querySelfPromise != null ) {
102
+ this . log ( 'joining existing self query' )
103
+ return this . querySelfPromise . promise
110
104
}
111
105
112
- if ( this . routingTable . size === 0 ) {
113
- let nextInterval = this . interval
106
+ this . querySelfPromise = pDefer ( )
114
107
115
- if ( this . initialQuerySelfHasRun != null ) {
116
- // if we've not yet run the first self query, shorten the interval until we try again
117
- nextInterval = this . initialInterval
118
- }
119
-
120
- this . log ( 'skip self-query because routing table is empty, will run again in %dms' , nextInterval )
121
- clearTimeout ( this . timeoutId )
122
- this . timeoutId = setTimeout ( this . querySelf . bind ( this ) , nextInterval )
123
- return
108
+ if ( this . routingTable . size === 0 ) {
109
+ // wait to discover at least one DHT peer
110
+ await pEvent ( this . routingTable , 'peer:add' )
124
111
}
125
112
126
- this . running = true
113
+ if ( this . started ) {
114
+ this . controller = new AbortController ( )
115
+ const signal = anySignal ( [ this . controller . signal , AbortSignal . timeout ( this . queryTimeout ) ] )
127
116
128
- Promise . resolve ( )
129
- . then ( async ( ) => {
130
- if ( ! this . started ) {
131
- this . log ( 'not running self-query - node stopped before query started' )
132
- return
117
+ // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
118
+ try {
119
+ if ( setMaxListeners != null ) {
120
+ setMaxListeners ( Infinity , signal )
133
121
}
122
+ } catch { } // fails on node < 15.4
134
123
135
- this . controller = new AbortController ( )
136
- const signal = anySignal ( [ this . controller . signal , AbortSignal . timeout ( this . queryTimeout ) ] )
137
-
138
- // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
139
- try {
140
- if ( setMaxListeners != null ) {
141
- setMaxListeners ( Infinity , signal )
142
- }
143
- } catch { } // fails on node < 15.4
144
-
145
- try {
146
- this . log ( 'run self-query, look for %d peers timing out after %dms' , this . count , this . queryTimeout )
147
-
148
- const found = await pipe (
149
- this . peerRouting . getClosestPeers ( this . components . peerId . toBytes ( ) , {
150
- signal,
151
- isSelfQuery : true
152
- } ) ,
153
- ( source ) => take ( source , this . count ) ,
154
- async ( source ) => length ( source )
155
- )
156
-
157
- this . log ( 'self-query ran successfully - found %d peers' , found )
158
-
159
- if ( this . initialQuerySelfHasRun != null ) {
160
- this . initialQuerySelfHasRun . resolve ( )
161
- this . initialQuerySelfHasRun = undefined
162
- }
163
- } catch ( err : any ) {
164
- this . log . error ( 'self-query error' , err )
165
- } finally {
166
- signal . clear ( )
167
- }
168
- } ) . catch ( err => {
169
- this . log ( 'self-query error' , err )
170
- } ) . finally ( ( ) => {
171
- this . running = false
124
+ try {
125
+ this . log ( 'run self-query, look for %d peers timing out after %dms' , this . count , this . queryTimeout )
126
+
127
+ const found = await pipe (
128
+ this . peerRouting . getClosestPeers ( this . components . peerId . toBytes ( ) , {
129
+ signal,
130
+ isSelfQuery : true
131
+ } ) ,
132
+ ( source ) => take ( source , this . count ) ,
133
+ async ( source ) => length ( source )
134
+ )
172
135
173
- clearTimeout ( this . timeoutId )
136
+ this . log ( 'self-query ran successfully - found %d peers' , found )
174
137
175
- if ( this . started ) {
176
- this . log ( 'running self-query again in %dms' , this . interval )
177
- this . timeoutId = setTimeout ( this . querySelf . bind ( this ) , this . interval )
138
+ if ( this . initialQuerySelfHasRun != null ) {
139
+ this . initialQuerySelfHasRun . resolve ( )
140
+ this . initialQuerySelfHasRun = undefined
178
141
}
179
- } )
142
+ } catch ( err : any ) {
143
+ this . log . error ( 'self-query error' , err )
144
+ } finally {
145
+ signal . clear ( )
146
+ }
147
+ }
148
+
149
+ this . querySelfPromise . resolve ( )
150
+ this . querySelfPromise = undefined
151
+
152
+ this . timeoutId = setTimeout ( ( ) => {
153
+ this . querySelf ( )
154
+ . catch ( err => {
155
+ this . log . error ( 'error running self-query' , err )
156
+ } )
157
+ } , this . interval )
180
158
}
181
159
}
0 commit comments