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