github.com/MetalBlockchain/subnet-evm@v0.4.9/core/bloombits/scheduler.go (about)

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