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  }