github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/protocols/blockvotes/bvprocessor/processor.go (about)

     1  package bvprocessor
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/unicornultrafoundation/go-helios/native/dag"
     8  	"github.com/unicornultrafoundation/go-helios/native/idx"
     9  	"github.com/unicornultrafoundation/go-helios/utils/datasemaphore"
    10  	"github.com/unicornultrafoundation/go-helios/utils/workers"
    11  
    12  	"github.com/unicornultrafoundation/go-u2u/native"
    13  )
    14  
    15  var (
    16  	ErrBusy = errors.New("failed to acquire events semaphore")
    17  )
    18  
    19  // Processor is responsible for processing incoming events
    20  type Processor struct {
    21  	cfg Config
    22  
    23  	quit chan struct{}
    24  	wg   sync.WaitGroup
    25  
    26  	callback Callback
    27  
    28  	inserter *workers.Workers
    29  
    30  	checker *workers.Workers
    31  
    32  	itemsSemaphore *datasemaphore.DataSemaphore
    33  }
    34  
    35  type ItemCallback struct {
    36  	Process  func(bvs native.LlrSignedBlockVotes) error
    37  	Released func(bvs native.LlrSignedBlockVotes, peer string, err error)
    38  	Check    func(bvs native.LlrSignedBlockVotes, checked func(error))
    39  }
    40  
    41  type Callback struct {
    42  	Item ItemCallback
    43  }
    44  
    45  // New creates an event processor
    46  func New(itemsSemaphore *datasemaphore.DataSemaphore, cfg Config, callback Callback) *Processor {
    47  	f := &Processor{
    48  		cfg:            cfg,
    49  		quit:           make(chan struct{}),
    50  		itemsSemaphore: itemsSemaphore,
    51  	}
    52  	released := callback.Item.Released
    53  	callback.Item.Released = func(bvs native.LlrSignedBlockVotes, peer string, err error) {
    54  		f.itemsSemaphore.Release(dag.Metric{Num: 1, Size: uint64(bvs.Size())})
    55  		if released != nil {
    56  			released(bvs, peer, err)
    57  		}
    58  	}
    59  	f.callback = callback
    60  	f.inserter = workers.New(&f.wg, f.quit, cfg.MaxTasks)
    61  	f.checker = workers.New(&f.wg, f.quit, cfg.MaxTasks)
    62  	return f
    63  }
    64  
    65  // Start boots up the items processor.
    66  func (f *Processor) Start() {
    67  	f.inserter.Start(1)
    68  	f.checker.Start(1)
    69  }
    70  
    71  // Stop interrupts the processor, canceling all the pending operations.
    72  // Stop waits until all the internal goroutines have finished.
    73  func (f *Processor) Stop() {
    74  	close(f.quit)
    75  	f.itemsSemaphore.Terminate()
    76  	f.wg.Wait()
    77  }
    78  
    79  // Overloaded returns true if too much items are being processed
    80  func (f *Processor) Overloaded() bool {
    81  	return f.TasksCount() > f.cfg.MaxTasks*3/4
    82  }
    83  
    84  type checkRes struct {
    85  	bvs native.LlrSignedBlockVotes
    86  	err error
    87  	pos idx.Event
    88  }
    89  
    90  func (f *Processor) Enqueue(peer string, items []native.LlrSignedBlockVotes, done func()) error {
    91  	totalSize := uint64(0)
    92  	for _, v := range items {
    93  		totalSize += v.Size()
    94  	}
    95  	if !f.itemsSemaphore.Acquire(dag.Metric{Num: idx.Event(len(items)), Size: totalSize}, f.cfg.SemaphoreTimeout) {
    96  		return ErrBusy
    97  	}
    98  
    99  	checkedC := make(chan *checkRes, len(items))
   100  	err := f.checker.Enqueue(func() {
   101  		for i, v := range items {
   102  			pos := idx.Event(i)
   103  			bvs := v
   104  			f.callback.Item.Check(bvs, func(err error) {
   105  				checkedC <- &checkRes{
   106  					bvs: bvs,
   107  					err: err,
   108  					pos: pos,
   109  				}
   110  			})
   111  		}
   112  	})
   113  	if err != nil {
   114  		return err
   115  	}
   116  	itemsLen := len(items)
   117  	return f.inserter.Enqueue(func() {
   118  		if done != nil {
   119  			defer done()
   120  		}
   121  
   122  		var orderedResults = make([]*checkRes, itemsLen)
   123  		var processed int
   124  		for processed < itemsLen {
   125  			select {
   126  			case res := <-checkedC:
   127  				orderedResults[res.pos] = res
   128  
   129  				for i := processed; processed < len(orderedResults) && orderedResults[i] != nil; i++ {
   130  					f.process(peer, orderedResults[i].bvs, orderedResults[i].err)
   131  					orderedResults[i] = nil // free the memory
   132  					processed++
   133  				}
   134  
   135  			case <-f.quit:
   136  				return
   137  			}
   138  		}
   139  	})
   140  }
   141  
   142  func (f *Processor) process(peer string, bvs native.LlrSignedBlockVotes, resErr error) {
   143  	// release item if failed validation
   144  	if resErr != nil {
   145  		f.callback.Item.Released(bvs, peer, resErr)
   146  		return
   147  	}
   148  	// process item
   149  	err := f.callback.Item.Process(bvs)
   150  	f.callback.Item.Released(bvs, peer, err)
   151  }
   152  
   153  func (f *Processor) TasksCount() int {
   154  	return f.inserter.TasksCount() + f.checker.TasksCount()
   155  }