@@ -27,6 +27,7 @@ import (
27
27
// lookup performs a network search for nodes close to the given target. It approaches the
28
28
// target by querying nodes that are closer to it on each iteration. The given target does
29
29
// not need to be an actual node identifier.
30
+ // lookup on an empty table will return immediately with no nodes.
30
31
type lookup struct {
31
32
tab * Table
32
33
queryfunc queryFunc
@@ -49,11 +50,15 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
49
50
result : nodesByDistance {target : target },
50
51
replyCh : make (chan []* enode.Node , alpha ),
51
52
cancelCh : ctx .Done (),
52
- queries : - 1 ,
53
53
}
54
54
// Don't query further if we hit ourself.
55
55
// Unlikely to happen often in practice.
56
56
it .asked [tab .self ().ID ()] = true
57
+ it .seen [tab .self ().ID ()] = true
58
+
59
+ // Initialize the lookup with nodes from table.
60
+ closest := it .tab .findnodeByID (it .result .target , bucketSize , false )
61
+ it .addNodes (closest .entries )
57
62
return it
58
63
}
59
64
@@ -64,22 +69,19 @@ func (it *lookup) run() []*enode.Node {
64
69
return it .result .entries
65
70
}
66
71
72
+ func (it * lookup ) empty () bool {
73
+ return len (it .replyBuffer ) == 0
74
+ }
75
+
67
76
// advance advances the lookup until any new nodes have been found.
68
77
// It returns false when the lookup has ended.
69
78
func (it * lookup ) advance () bool {
70
79
for it .startQueries () {
71
80
select {
72
81
case nodes := <- it .replyCh :
73
- it .replyBuffer = it .replyBuffer [:0 ]
74
- for _ , n := range nodes {
75
- if n != nil && ! it .seen [n .ID ()] {
76
- it .seen [n .ID ()] = true
77
- it .result .push (n , bucketSize )
78
- it .replyBuffer = append (it .replyBuffer , n )
79
- }
80
- }
81
82
it .queries --
82
- if len (it .replyBuffer ) > 0 {
83
+ it .addNodes (nodes )
84
+ if ! it .empty () {
83
85
return true
84
86
}
85
87
case <- it .cancelCh :
@@ -89,6 +91,17 @@ func (it *lookup) advance() bool {
89
91
return false
90
92
}
91
93
94
+ func (it * lookup ) addNodes (nodes []* enode.Node ) {
95
+ it .replyBuffer = it .replyBuffer [:0 ]
96
+ for _ , n := range nodes {
97
+ if n != nil && ! it .seen [n .ID ()] {
98
+ it .seen [n .ID ()] = true
99
+ it .result .push (n , bucketSize )
100
+ it .replyBuffer = append (it .replyBuffer , n )
101
+ }
102
+ }
103
+ }
104
+
92
105
func (it * lookup ) shutdown () {
93
106
for it .queries > 0 {
94
107
<- it .replyCh
@@ -103,20 +116,6 @@ func (it *lookup) startQueries() bool {
103
116
return false
104
117
}
105
118
106
- // The first query returns nodes from the local table.
107
- if it .queries == - 1 {
108
- closest := it .tab .findnodeByID (it .result .target , bucketSize , false )
109
- // Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
110
- // for the table to fill in this case, but there is no good mechanism for that
111
- // yet.
112
- if len (closest .entries ) == 0 {
113
- it .slowdown ()
114
- }
115
- it .queries = 1
116
- it .replyCh <- closest .entries
117
- return true
118
- }
119
-
120
119
// Ask the closest nodes that we haven't asked yet.
121
120
for i := 0 ; i < len (it .result .entries ) && it .queries < alpha ; i ++ {
122
121
n := it .result .entries [i ]
@@ -130,15 +129,6 @@ func (it *lookup) startQueries() bool {
130
129
return it .queries > 0
131
130
}
132
131
133
- func (it * lookup ) slowdown () {
134
- sleep := time .NewTimer (1 * time .Second )
135
- defer sleep .Stop ()
136
- select {
137
- case <- sleep .C :
138
- case <- it .tab .closeReq :
139
- }
140
- }
141
-
142
132
func (it * lookup ) query (n * enode.Node , reply chan <- []* enode.Node ) {
143
133
r , err := it .queryfunc (n )
144
134
if ! errors .Is (err , errClosed ) { // avoid recording failures on shutdown.
@@ -153,12 +143,16 @@ func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
153
143
154
144
// lookupIterator performs lookup operations and iterates over all seen nodes.
155
145
// When a lookup finishes, a new one is created through nextLookup.
146
+ // LookupIterator waits for table initialization and triggers a table refresh
147
+ // when necessary.
148
+
156
149
type lookupIterator struct {
157
- buffer []* enode.Node
158
- nextLookup lookupFunc
159
- ctx context.Context
160
- cancel func ()
161
- lookup * lookup
150
+ buffer []* enode.Node
151
+ nextLookup lookupFunc
152
+ ctx context.Context
153
+ cancel func ()
154
+ lookup * lookup
155
+ tabRefreshing <- chan struct {}
162
156
}
163
157
164
158
type lookupFunc func (ctx context.Context ) * lookup
@@ -182,6 +176,7 @@ func (it *lookupIterator) Next() bool {
182
176
if len (it .buffer ) > 0 {
183
177
it .buffer = it .buffer [1 :]
184
178
}
179
+
185
180
// Advance the lookup to refill the buffer.
186
181
for len (it .buffer ) == 0 {
187
182
if it .ctx .Err () != nil {
@@ -191,17 +186,55 @@ func (it *lookupIterator) Next() bool {
191
186
}
192
187
if it .lookup == nil {
193
188
it .lookup = it .nextLookup (it .ctx )
189
+ if it .lookup .empty () {
190
+ // If the lookup is empty right after creation, it means the local table
191
+ // is in a degraded state, and we need to wait for it to fill again.
192
+ it .lookupFailed (it .lookup .tab , 1 * time .Minute )
193
+ it .lookup = nil
194
+ continue
195
+ }
196
+ // Yield the initial nodes from the iterator before advancing the lookup.
197
+ it .buffer = it .lookup .replyBuffer
194
198
continue
195
199
}
196
- if ! it .lookup .advance () {
200
+
201
+ newNodes := it .lookup .advance ()
202
+ it .buffer = it .lookup .replyBuffer
203
+ if ! newNodes {
197
204
it .lookup = nil
198
- continue
199
205
}
200
- it .buffer = it .lookup .replyBuffer
201
206
}
202
207
return true
203
208
}
204
209
210
+ // lookupFailed handles failed lookup attempts. This can be called when the table has
211
+ // exited, or when it runs out of nodes.
212
+ func (it * lookupIterator ) lookupFailed (tab * Table , timeout time.Duration ) {
213
+ tout , cancel := context .WithTimeout (it .ctx , timeout )
214
+ defer cancel ()
215
+
216
+ // Wait for Table initialization to complete, in case it is still in progress.
217
+ select {
218
+ case <- tab .initDone :
219
+ case <- tout .Done ():
220
+ return
221
+ }
222
+
223
+ // Wait for ongoing refresh operation, or trigger one.
224
+ if it .tabRefreshing == nil {
225
+ it .tabRefreshing = tab .refresh ()
226
+ }
227
+ select {
228
+ case <- it .tabRefreshing :
229
+ it .tabRefreshing = nil
230
+ case <- tout .Done ():
231
+ return
232
+ }
233
+
234
+ // Wait for the table to fill.
235
+ tab .waitForNodes (tout , 1 )
236
+ }
237
+
205
238
// Close ends the iterator.
206
239
func (it * lookupIterator ) Close () {
207
240
it .cancel ()
0 commit comments