github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/pubsub/x_sp_subscriber.go (about)

     1  package pubsub
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"sync/atomic"
     7  
     8  	"github.com/benz9527/toy-box/algo/queue"
     9  )
    10  
    11  type subscriberStatus int32
    12  
    13  const (
    14  	subReady subscriberStatus = iota
    15  	subRunning
    16  )
    17  
    18  const (
    19  	activeSpin  = 4
    20  	passiveSpin = 2
    21  )
    22  
    23  type xSinglePipelineSubscriber[T any] struct {
    24  	rb       queue.RingBuffer[T]
    25  	seq      Sequencer
    26  	strategy BlockStrategy
    27  	handler  EventHandler[T]
    28  	status   subscriberStatus
    29  	spin     int32
    30  }
    31  
    32  func newXSinglePipelineSubscriber[T any](
    33  	rb queue.RingBuffer[T],
    34  	handler EventHandler[T],
    35  	seq Sequencer,
    36  	strategy BlockStrategy,
    37  ) *xSinglePipelineSubscriber[T] {
    38  	ncpu := runtime.NumCPU()
    39  	spin := 0
    40  	if ncpu > 1 {
    41  		spin = activeSpin
    42  	}
    43  	return &xSinglePipelineSubscriber[T]{
    44  		status:   subReady,
    45  		seq:      seq,
    46  		rb:       rb,
    47  		strategy: strategy,
    48  		handler:  handler,
    49  		spin:     int32(spin),
    50  	}
    51  }
    52  
    53  func (sub *xSinglePipelineSubscriber[T]) Start() error {
    54  	if atomic.CompareAndSwapInt32((*int32)(&sub.status), int32(subReady), int32(subRunning)) {
    55  		go sub.eventsHandle()
    56  		return nil
    57  	}
    58  	return fmt.Errorf("subscriber already started")
    59  }
    60  
    61  func (sub *xSinglePipelineSubscriber[T]) Stop() error {
    62  	if atomic.CompareAndSwapInt32((*int32)(&sub.status), int32(subRunning), int32(subReady)) {
    63  		sub.strategy.Done()
    64  		return nil
    65  	}
    66  	return fmt.Errorf("subscriber already stopped")
    67  }
    68  
    69  func (sub *xSinglePipelineSubscriber[T]) IsStopped() bool {
    70  	return atomic.LoadInt32((*int32)(&sub.status)) == int32(subReady)
    71  }
    72  
    73  func (sub *xSinglePipelineSubscriber[T]) eventsHandle() {
    74  	readCursor := sub.seq.GetReadCursor().Load()
    75  	spin := sub.spin
    76  	for {
    77  		if sub.IsStopped() {
    78  			return
    79  		}
    80  		spinCount := int32(0)
    81  		for {
    82  			if sub.IsStopped() {
    83  				return
    84  			}
    85  			e := sub.rb.LoadEntryByCursor(readCursor)
    86  			if e.GetCursor() == readCursor {
    87  				_ = sub.HandleEvent(e.GetValue())
    88  				spinCount = 0
    89  				// FIXME handle error
    90  				readCursor = sub.seq.GetReadCursor().Next()
    91  				break
    92  			} else {
    93  				if spinCount < spin {
    94  					procYield(30)
    95  				} else if spinCount < spin+passiveSpin {
    96  					runtime.Gosched()
    97  				} else {
    98  					sub.strategy.WaitFor(func() bool {
    99  						e := sub.rb.LoadEntryByCursor(readCursor)
   100  						return e.GetCursor() == readCursor
   101  					})
   102  					spinCount = 0
   103  				}
   104  				spinCount++
   105  			}
   106  		}
   107  	}
   108  }
   109  
   110  func (sub *xSinglePipelineSubscriber[T]) HandleEvent(event T) error {
   111  	//defer sub.strategy.Done() // Slow performance issue
   112  	err := sub.handler(event)
   113  	return err
   114  }