github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/core/bloombits/scheduler.go (about)

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