github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/routing/inmemory/inmemory.go (about) 1 package inmemory 2 3 import ( 4 "context" 5 "time" 6 7 sync "github.com/bacalhau-project/golang-mutex-tracer" 8 "github.com/filecoin-project/bacalhau/pkg/model" 9 "github.com/filecoin-project/bacalhau/pkg/requester" 10 "github.com/filecoin-project/bacalhau/pkg/routing" 11 "github.com/libp2p/go-libp2p/core/peer" 12 "github.com/rs/zerolog/log" 13 ) 14 15 // TODO: replace the manual and lazy eviction with a more efficient caching library 16 type nodeInfoWrapper struct { 17 model.NodeInfo 18 evictAt time.Time 19 } 20 21 type NodeInfoStoreParams struct { 22 TTL time.Duration 23 } 24 25 type NodeInfoStore struct { 26 ttl time.Duration 27 nodeInfoMap map[peer.ID]nodeInfoWrapper 28 engineNodeIDMap map[model.Engine]map[peer.ID]struct{} 29 mu sync.RWMutex 30 } 31 32 func NewNodeInfoStore(params NodeInfoStoreParams) *NodeInfoStore { 33 res := &NodeInfoStore{ 34 ttl: params.TTL, 35 nodeInfoMap: make(map[peer.ID]nodeInfoWrapper), 36 engineNodeIDMap: make(map[model.Engine]map[peer.ID]struct{}), 37 } 38 res.mu.EnableTracerWithOpts(sync.Opts{ 39 Threshold: 10 * time.Millisecond, 40 Id: "InMemoryNodeInfoStore.mu", 41 }) 42 return res 43 } 44 45 func (r *NodeInfoStore) Add(ctx context.Context, nodeInfo model.NodeInfo) error { 46 r.mu.Lock() 47 defer r.mu.Unlock() 48 49 // delete node from previous engines if it already exists to replace old engines with new ones if they've changed 50 existingNodeInfo, ok := r.nodeInfoMap[nodeInfo.PeerInfo.ID] 51 if ok { 52 for _, engine := range existingNodeInfo.ComputeNodeInfo.ExecutionEngines { 53 delete(r.engineNodeIDMap[engine], nodeInfo.PeerInfo.ID) 54 } 55 } 56 57 // TODO: use data structure that maintains nodes in descending order based on available capacity. 58 for _, engine := range nodeInfo.ComputeNodeInfo.ExecutionEngines { 59 if _, ok := r.engineNodeIDMap[engine]; !ok { 60 r.engineNodeIDMap[engine] = make(map[peer.ID]struct{}) 61 } 62 r.engineNodeIDMap[engine][nodeInfo.PeerInfo.ID] = struct{}{} 63 } 64 65 // add or update the node info 66 r.nodeInfoMap[nodeInfo.PeerInfo.ID] = nodeInfoWrapper{ 67 NodeInfo: nodeInfo, 68 evictAt: time.Now().Add(r.ttl), 69 } 70 71 log.Ctx(ctx).Trace().Msgf("Added node info %+v", nodeInfo) 72 return nil 73 } 74 75 func (r *NodeInfoStore) Get(ctx context.Context, peerID peer.ID) (model.NodeInfo, error) { 76 r.mu.RLock() 77 defer r.mu.RUnlock() 78 infoWrapper, ok := r.nodeInfoMap[peerID] 79 if !ok { 80 return model.NodeInfo{}, requester.NewErrNodeNotFound(peerID) 81 } 82 if time.Now().After(infoWrapper.evictAt) { 83 go r.evict(ctx, infoWrapper) 84 return model.NodeInfo{}, requester.NewErrNodeNotFound(peerID) 85 } 86 return infoWrapper.NodeInfo, nil 87 } 88 89 func (r *NodeInfoStore) FindPeer(ctx context.Context, peerID peer.ID) (peer.AddrInfo, error) { 90 r.mu.RLock() 91 defer r.mu.RUnlock() 92 infoWrapper, ok := r.nodeInfoMap[peerID] 93 if !ok { 94 return peer.AddrInfo{}, nil 95 } 96 if len(infoWrapper.PeerInfo.Addrs) > 0 { 97 return infoWrapper.PeerInfo, nil 98 } 99 return peer.AddrInfo{}, nil 100 } 101 102 func (r *NodeInfoStore) List(ctx context.Context) ([]model.NodeInfo, error) { 103 r.mu.RLock() 104 defer r.mu.RUnlock() 105 var nodeInfos []model.NodeInfo 106 var toEvict []nodeInfoWrapper 107 for _, nodeInfo := range r.nodeInfoMap { 108 if time.Now().After(nodeInfo.evictAt) { 109 toEvict = append(toEvict, nodeInfo) 110 } else { 111 nodeInfos = append(nodeInfos, nodeInfo.NodeInfo) 112 } 113 } 114 if len(toEvict) > 0 { 115 go r.evict(ctx, toEvict...) 116 } 117 return nodeInfos, nil 118 } 119 120 func (r *NodeInfoStore) ListForEngine(ctx context.Context, engine model.Engine) ([]model.NodeInfo, error) { 121 r.mu.RLock() 122 defer r.mu.RUnlock() 123 var nodeInfos []model.NodeInfo 124 var toEvict []nodeInfoWrapper 125 for nodeID := range r.engineNodeIDMap[engine] { 126 nodeInfo := r.nodeInfoMap[nodeID] 127 if time.Now().After(nodeInfo.evictAt) { 128 toEvict = append(toEvict, nodeInfo) 129 } else { 130 nodeInfos = append(nodeInfos, nodeInfo.NodeInfo) 131 } 132 } 133 if len(toEvict) > 0 { 134 go r.evict(ctx, toEvict...) 135 } 136 return nodeInfos, nil 137 } 138 139 func (r *NodeInfoStore) Delete(ctx context.Context, peerID peer.ID) error { 140 r.mu.Lock() 141 defer r.mu.Unlock() 142 return r.doDelete(ctx, peerID) 143 } 144 145 func (r *NodeInfoStore) evict(ctx context.Context, infoWrappers ...nodeInfoWrapper) { 146 r.mu.Lock() 147 defer r.mu.Unlock() 148 for _, infoWrapper := range infoWrappers { 149 nodeInfo, ok := r.nodeInfoMap[infoWrapper.PeerInfo.ID] 150 if !ok || nodeInfo.evictAt != infoWrapper.evictAt { 151 return // node info already evicted or has been updated since it was scheduled for eviction 152 } 153 err := r.doDelete(ctx, infoWrapper.PeerInfo.ID) 154 if err != nil { 155 log.Ctx(ctx).Warn().Err(err).Msgf("Failed to evict expired node info for peer %s", infoWrapper.PeerInfo.ID) 156 } 157 } 158 } 159 160 func (r *NodeInfoStore) doDelete(ctx context.Context, peerID peer.ID) error { 161 nodeInfo, ok := r.nodeInfoMap[peerID] 162 if !ok { 163 return nil 164 } 165 for _, engine := range nodeInfo.ComputeNodeInfo.ExecutionEngines { 166 delete(r.engineNodeIDMap[engine], peerID) 167 } 168 delete(r.nodeInfoMap, peerID) 169 return nil 170 } 171 172 // compile time check that we implement the interface 173 var _ routing.NodeInfoStore = (*NodeInfoStore)(nil)