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  }