github.com/fff-chain/go-fff@v0.0.0-20220726032732-1c84420b8a99/core/bloombits/scheduler.go (about)

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