addrmgr: make safe accessors for addresses #1760
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This resolves a data race I have observed with both stock btcwallet run in spv mode, and a different app using neutrino's
ChainService
. The races are at the end of this PR description. The explanation and resolution are as follows.NOTE: An alternative resolution that adds a mutex to
KnownAddress
is proposed in #1763The
KnownAddress
returned by(*AddrManager).GetAddress
is not safely usable while theAddrManager
is in use by another goroutine since the fields of theKnownAddress
are mutable. AlthoughAddrManager
treats a*wire.NetAddress
as immutable, theKnownAddress.na
field itself is reassigned. Similarly, thelastattempt
field is mutable.To address this API usability issue, this commit makes two new accessor methods:
GetNetAddress
returns the*wire.NetAddress
and thelastAttempt
GetAddressCopy
returns a copy of the*KnownAddress
. This is not used in the fixednewAddressFunc
, but I've created it for any previousGetAddress
consumers that do want a fullKnownAddress
for whatever reason.The original
GetAddress
remains because it is designed to reference theKnownAddress
that is still in theAddrManager
, allowing operations by theAddrManager
such as connects and attempts to be reflected in the initially returnedKnownAddress
. The blackboxaddrmgr
package tests are are designed around this behavior.However, it is not safe to use
GetAddress
from another goroutine such as theconnmgr
'sGetNewAddress
function (thenewAddressFunc
closure defined innewServer
) concurrent with updates to theKnownAddress
such as via(*Peer).inHandler
, which has callbacks to theAddrManager
that may mutate theKnownAddress
.An alternate resolution would involve adding a mutex to a
KnownAddress
, but this would be a more substantial change, which creates unnecessary lock contention and complexity. Returning copies or just the needed fields is safer and simpler.While the following data races are using neutrino's
ChainService
, the root cause traces back to the btcd API issue above. In both cases it comes down to use of theKnownAddress
returned byGetAddress
in thenewAddressFunc
closure, concurrent with a write elsewhere.Data race with custom spv wallet app using neutrino.
Data race with stock btcwallet in spv mode (using neutrino). Partially incomplete dump it seems.
I also have updates ready to neutrino and btcwallet that use this change.