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 }