github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/ipc/x_publisher.go (about) 1 package ipc 2 3 import ( 4 "context" 5 "log/slog" 6 "runtime" 7 "sync/atomic" 8 "time" 9 10 "github.com/benz9527/xboot/lib/infra" 11 "github.com/benz9527/xboot/lib/queue" 12 ) 13 14 type publisherStatus int32 15 16 const ( 17 pubReady publisherStatus = iota 18 pubRunning 19 ) 20 21 var ( 22 _ Publisher[int] = (*xPublisher[int])(nil) 23 _ stopper = (*xPublisher[int])(nil) 24 ) 25 26 type xPublisher[T any] struct { 27 seq Sequencer 28 rb queue.RingBuffer[T] 29 strategy BlockStrategy 30 capacity uint64 31 status publisherStatus 32 } 33 34 func newXPublisher[T any](seq Sequencer, rb queue.RingBuffer[T], strategy BlockStrategy) *xPublisher[T] { 35 return &xPublisher[T]{ 36 seq: seq, 37 rb: rb, 38 strategy: strategy, 39 capacity: seq.Capacity(), 40 status: pubReady, 41 } 42 } 43 44 func (pub *xPublisher[T]) Start() error { 45 if atomic.CompareAndSwapInt32((*int32)(&pub.status), int32(pubReady), int32(pubRunning)) { 46 return nil 47 } 48 return infra.NewErrorStack("[disruptor] publisher already started") 49 } 50 51 func (pub *xPublisher[T]) Stop() error { 52 if atomic.CompareAndSwapInt32((*int32)(&pub.status), int32(pubRunning), int32(pubReady)) { 53 return nil 54 } 55 return infra.NewErrorStack("[disruptor] publisher already stopped") 56 } 57 58 func (pub *xPublisher[T]) IsStopped() bool { 59 return atomic.LoadInt32((*int32)(&pub.status)) == int32(pubReady) 60 } 61 62 func (pub *xPublisher[T]) Publish(event T) (uint64, bool, error) { 63 if pub.IsStopped() { 64 return 0, false, infra.NewErrorStack("[disruptor] publisher closed") 65 } 66 nextWriteCursor := pub.seq.GetWriteCursor().Next() 67 for { 68 readCursor := pub.seq.GetReadCursor().Load() 69 if nextWriteCursor-readCursor <= pub.capacity { 70 pub.rb.LoadEntryByCursor(nextWriteCursor-1).Store(nextWriteCursor-1, event) 71 pub.strategy.Done() 72 return nextWriteCursor - 1, true, nil 73 } else { 74 pub.strategy.Done() 75 } 76 runtime.Gosched() 77 if pub.IsStopped() { 78 return 0, false, infra.NewErrorStack("[disruptor] publisher closed") 79 } 80 } 81 } 82 83 func (pub *xPublisher[T]) PublishTimeout(event T, timeout time.Duration) { 84 go func() { 85 if pub.IsStopped() { 86 slog.Warn("publisher closed", "event", event) 87 return 88 } 89 nextCursor := pub.seq.GetWriteCursor().Next() 90 var ok bool 91 ctx, cancel := context.WithTimeout(context.Background(), timeout) 92 defer cancel() 93 for { 94 select { 95 case <-ctx.Done(): 96 slog.Warn("publish timeout", "event", event) 97 return 98 default: 99 if ok = pub.publishAt(event, nextCursor-1); ok { 100 slog.Info("publish success", "event", event) 101 return 102 } 103 } 104 runtime.Gosched() 105 if pub.IsStopped() { 106 slog.Warn("publisher closed", "event", event) 107 return 108 } 109 } 110 }() 111 } 112 113 // unstable result under concurrent scenario 114 func (pub *xPublisher[T]) tryWriteWindow() int { 115 tryNext := pub.seq.GetWriteCursor().Load() + 1 116 readCursor := pub.seq.GetReadCursor().Load() - 1 117 if tryNext < readCursor+pub.capacity { 118 return int(readCursor + pub.capacity - tryNext) 119 } 120 return -int(tryNext - (readCursor + pub.capacity)) 121 } 122 123 func (pub *xPublisher[T]) publishAt(event T, cursor uint64) bool { 124 readCursor := pub.seq.GetReadCursor().Load() - 1 125 if cursor > readCursor+pub.seq.Capacity() { 126 return false 127 } 128 pub.rb.LoadEntryByCursor(readCursor).Store(cursor, event) 129 pub.strategy.Done() 130 return true 131 }