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