github.com/MetalBlockchain/subnet-evm@v0.4.9/core/bloombits/scheduler.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2017 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package bloombits 28 29 import ( 30 "sync" 31 ) 32 33 // request represents a bloom retrieval task to prioritize and pull from the local 34 // database or remotely from the network. 35 type request struct { 36 section uint64 // Section index to retrieve the a bit-vector from 37 bit uint // Bit index within the section to retrieve the vector of 38 } 39 40 // response represents the state of a requested bit-vector through a scheduler. 41 type response struct { 42 cached []byte // Cached bits to dedup multiple requests 43 done chan struct{} // Channel to allow waiting for completion 44 } 45 46 // scheduler handles the scheduling of bloom-filter retrieval operations for 47 // entire section-batches belonging to a single bloom bit. Beside scheduling the 48 // retrieval operations, this struct also deduplicates the requests and caches 49 // the results to minimize network/database overhead even in complex filtering 50 // scenarios. 51 type scheduler struct { 52 bit uint // Index of the bit in the bloom filter this scheduler is responsible for 53 responses map[uint64]*response // Currently pending retrieval requests or already cached responses 54 lock sync.Mutex // Lock protecting the responses from concurrent access 55 } 56 57 // newScheduler creates a new bloom-filter retrieval scheduler for a specific 58 // bit index. 59 func newScheduler(idx uint) *scheduler { 60 return &scheduler{ 61 bit: idx, 62 responses: make(map[uint64]*response), 63 } 64 } 65 66 // run creates a retrieval pipeline, receiving section indexes from sections and 67 // returning the results in the same order through the done channel. Concurrent 68 // runs of the same scheduler are allowed, leading to retrieval task deduplication. 69 func (s *scheduler) run(sections chan uint64, dist chan *request, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { 70 // Create a forwarder channel between requests and responses of the same size as 71 // the distribution channel (since that will block the pipeline anyway). 72 pend := make(chan uint64, cap(dist)) 73 74 // Start the pipeline schedulers to forward between user -> distributor -> user 75 wg.Add(2) 76 go s.scheduleRequests(sections, dist, pend, quit, wg) 77 go s.scheduleDeliveries(pend, done, quit, wg) 78 } 79 80 // reset cleans up any leftovers from previous runs. This is required before a 81 // restart to ensure the no previously requested but never delivered state will 82 // cause a lockup. 83 func (s *scheduler) reset() { 84 s.lock.Lock() 85 defer s.lock.Unlock() 86 87 for section, res := range s.responses { 88 if res.cached == nil { 89 delete(s.responses, section) 90 } 91 } 92 } 93 94 // scheduleRequests reads section retrieval requests from the input channel, 95 // deduplicates the stream and pushes unique retrieval tasks into the distribution 96 // channel for a database or network layer to honour. 97 func (s *scheduler) scheduleRequests(reqs chan uint64, dist chan *request, pend chan uint64, quit chan struct{}, wg *sync.WaitGroup) { 98 // Clean up the goroutine and pipeline when done 99 defer wg.Done() 100 defer close(pend) 101 102 // Keep reading and scheduling section requests 103 for { 104 select { 105 case <-quit: 106 return 107 108 case section, ok := <-reqs: 109 // New section retrieval requested 110 if !ok { 111 return 112 } 113 // Deduplicate retrieval requests 114 unique := false 115 116 s.lock.Lock() 117 if s.responses[section] == nil { 118 s.responses[section] = &response{ 119 done: make(chan struct{}), 120 } 121 unique = true 122 } 123 s.lock.Unlock() 124 125 // Schedule the section for retrieval and notify the deliverer to expect this section 126 if unique { 127 select { 128 case <-quit: 129 return 130 case dist <- &request{bit: s.bit, section: section}: 131 } 132 } 133 select { 134 case <-quit: 135 return 136 case pend <- section: 137 } 138 } 139 } 140 } 141 142 // scheduleDeliveries reads section acceptance notifications and waits for them 143 // to be delivered, pushing them into the output data buffer. 144 func (s *scheduler) scheduleDeliveries(pend chan uint64, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { 145 // Clean up the goroutine and pipeline when done 146 defer wg.Done() 147 defer close(done) 148 149 // Keep reading notifications and scheduling deliveries 150 for { 151 select { 152 case <-quit: 153 return 154 155 case idx, ok := <-pend: 156 // New section retrieval pending 157 if !ok { 158 return 159 } 160 // Wait until the request is honoured 161 s.lock.Lock() 162 res := s.responses[idx] 163 s.lock.Unlock() 164 165 select { 166 case <-quit: 167 return 168 case <-res.done: 169 } 170 // Deliver the result 171 select { 172 case <-quit: 173 return 174 case done <- res.cached: 175 } 176 } 177 } 178 } 179 180 // deliver is called by the request distributor when a reply to a request arrives. 181 func (s *scheduler) deliver(sections []uint64, data [][]byte) { 182 s.lock.Lock() 183 defer s.lock.Unlock() 184 185 for i, section := range sections { 186 if res := s.responses[section]; res != nil && res.cached == nil { // Avoid non-requests and double deliveries 187 res.cached = data[i] 188 close(res.done) 189 } 190 } 191 }