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  }