channeldb: avoid locking the graph cache while iterating channels

It may happen that we do pathfinding while also attempt to change the
graph. In this case changing the database, channel state, graph while
reading from the same sources may create deadlocks. To resolve this we
change locking policy in the graph cache when pathfinding.
This commit is contained in:
Andras Banki-Horvath 2021-11-06 21:22:45 +01:00
parent 54584aabb6
commit 3df216f6c3
No known key found for this signature in database
GPG Key ID: 80E5375C094198D8

View File

@ -401,10 +401,9 @@ func (c *GraphCache) UpdateChannel(info *ChannelEdgeInfo) {
}
}
// ForEachChannel invokes the given callback for each channel of the given node.
func (c *GraphCache) ForEachChannel(node route.Vertex,
cb func(channel *DirectedChannel) error) error {
// getChannels returns a copy of the passed node's channels or nil if there
// isn't any.
func (c *GraphCache) getChannels(node route.Vertex) []*DirectedChannel {
c.mtx.RLock()
defer c.mtx.RUnlock()
@ -428,6 +427,8 @@ func (c *GraphCache) ForEachChannel(node route.Vertex,
return node
}
i := 0
channelsCopy := make([]*DirectedChannel, len(channels))
for _, channel := range channels {
// We need to copy the channel and policy to avoid it being
// updated in the cache if the path finding algorithm sets
@ -439,9 +440,30 @@ func (c *GraphCache) ForEachChannel(node route.Vertex,
channelCopy.InPolicy.ToNodeFeatures = features
}
if err := cb(channelCopy); err != nil {
channelsCopy[i] = channelCopy
i++
}
return channelsCopy
}
// ForEachChannel invokes the given callback for each channel of the given node.
func (c *GraphCache) ForEachChannel(node route.Vertex,
cb func(channel *DirectedChannel) error) error {
// Obtain a copy of the node's channels. We need do this in order to
// avoid deadlocks caused by interaction with the graph cache, channel
// state and the graph database from multiple goroutines. This snapshot
// is only used for path finding where being stale is acceptable since
// the real world graph and our representation may always become
// slightly out of sync for a short time and the actual channel state
// is stored separately.
channels := c.getChannels(node)
for _, channel := range channels {
if err := cb(channel); err != nil {
return err
}
}
return nil