@@ -253,6 +253,55 @@ func TestSeenKeepsConcurrentPut(t *testing.T) {
253253 }
254254}
255255
256+ // TestSeenRaceWithRemove pins Seen's read-modify-write against a concurrent
257+ // Remove of the same overlay. Remove takes the same lock as Put and Seen, so
258+ // it cannot run while Seen holds the entry it has just read: the removal
259+ // always wins over Seen's stale write-back.
260+ func TestSeenRaceWithRemove (t * testing.T ) {
261+ t .Parallel ()
262+
263+ now := time .Unix (1_000_000 , 0 )
264+ hooked := & hookStore {StateStorer : mock .NewStateStore ()}
265+ book := addressbook .NewWithClock (hooked , func () time.Time { return now })
266+
267+ overlay := swarm .NewAddress ([]byte {0 , 1 , 2 , 3 })
268+ addr := newTestAddr (t , overlay )
269+
270+ if err := book .Put (overlay , addr , true ); err != nil {
271+ t .Fatal (err )
272+ }
273+
274+ // move past the throttle window, so that Seen takes its write path.
275+ now = now .Add (25 * time .Hour )
276+
277+ // While Seen holds the entry it has just read, kademlia decides the peer is
278+ // bad (too many failed connection attempts, overlay mismatch, light node)
279+ // and removes it.
280+ started , finished := make (chan struct {}), make (chan struct {})
281+ hooked .onGet = func () {
282+ go func () {
283+ defer close (finished )
284+ close (started )
285+ if err := book .Remove (overlay ); err != nil {
286+ t .Error (err )
287+ }
288+ }()
289+ <- started
290+ // Give Remove a chance to run before Seen writes back. Serialized on
291+ // the addressbook lock, it blocks here until Seen returns.
292+ time .Sleep (100 * time .Millisecond )
293+ }
294+
295+ if err := book .Seen (overlay ); err != nil {
296+ t .Fatal (err )
297+ }
298+ <- finished
299+
300+ if _ , _ , err := book .Get (overlay ); ! errors .Is (err , addressbook .ErrNotFound ) {
301+ t .Fatalf ("concurrent Remove was undone by Seen's stale write-back: err=%v" , err )
302+ }
303+ }
304+
256305// hookStore fires onGet once, immediately after a Get returns, to interleave a
257306// concurrent writer inside Seen's read-modify-write.
258307type hookStore struct {
0 commit comments