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