bus.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package eventbus
  2. import (
  3. "sync"
  4. "time"
  5. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  6. )
  7. // DefaultBufferSize is the number of events the bus can hold before Publish starts dropping.
  8. const DefaultBufferSize = 256
  9. // subscriber pairs an ID with its event handler.
  10. type subscriber struct {
  11. id string
  12. handler func(Event)
  13. }
  14. // Bus is a minimal in-process pub/sub event bus backed by a buffered channel.
  15. // Producers call Publish (non-blocking) and every event is fanned out to all
  16. // subscribers; per-event filtering is the subscriber's responsibility.
  17. type Bus struct {
  18. ch chan Event
  19. subs []subscriber
  20. mu sync.RWMutex
  21. done chan struct{}
  22. wg sync.WaitGroup
  23. }
  24. // New creates a Bus with the given buffer size. Use 0 for DefaultBufferSize.
  25. func New(bufSize int) *Bus {
  26. if bufSize <= 0 {
  27. bufSize = DefaultBufferSize
  28. }
  29. b := &Bus{
  30. ch: make(chan Event, bufSize),
  31. done: make(chan struct{}),
  32. }
  33. b.wg.Add(1)
  34. go b.dispatch()
  35. return b
  36. }
  37. // Subscribe registers a handler that receives every published event.
  38. // The id is used for Unsubscribe; it must be unique across active subscribers.
  39. // Subscribing with an already-registered id replaces the previous handler.
  40. func (b *Bus) Subscribe(id string, handler func(Event)) {
  41. b.mu.Lock()
  42. defer b.mu.Unlock()
  43. for i, s := range b.subs {
  44. if s.id == id {
  45. b.subs[i].handler = handler
  46. return
  47. }
  48. }
  49. b.subs = append(b.subs, subscriber{id: id, handler: handler})
  50. }
  51. // Unsubscribe removes a subscriber by id. Safe to call with unknown id.
  52. func (b *Bus) Unsubscribe(id string) {
  53. b.mu.Lock()
  54. defer b.mu.Unlock()
  55. for i, s := range b.subs {
  56. if s.id == id {
  57. b.subs = append(b.subs[:i], b.subs[i+1:]...)
  58. return
  59. }
  60. }
  61. }
  62. // Publish sends an event to all subscribers. Non-blocking — if the buffer is
  63. // full the event is dropped and a warning is logged.
  64. func (b *Bus) Publish(e Event) {
  65. if e.Timestamp.IsZero() {
  66. e.Timestamp = time.Now()
  67. }
  68. select {
  69. case b.ch <- e:
  70. default:
  71. logger.Warning("eventbus: buffer full, dropping event ", e.Type)
  72. }
  73. }
  74. // dispatch is the fan-out loop. It reads events from the channel and hands each
  75. // one to every subscriber's handler in its own goroutine, so a subscriber whose
  76. // handler blocks on network I/O (the email and Telegram notifiers can block for
  77. // tens of seconds) cannot stall delivery of unrelated, higher-value events such
  78. // as xray.crash or node.down.
  79. func (b *Bus) dispatch() {
  80. defer b.wg.Done()
  81. for {
  82. select {
  83. case e, ok := <-b.ch:
  84. if !ok {
  85. return
  86. }
  87. b.mu.RLock()
  88. subs := make([]subscriber, len(b.subs))
  89. copy(subs, b.subs)
  90. b.mu.RUnlock()
  91. for _, s := range subs {
  92. go safeCall(s.handler, e)
  93. }
  94. case <-b.done:
  95. return
  96. }
  97. }
  98. }
  99. // safeCall invokes handler with panic recovery.
  100. func safeCall(fn func(Event), e Event) {
  101. defer func() {
  102. if r := recover(); r != nil {
  103. logger.Errorf("eventbus: subscriber panicked on %s: %v", e.Type, r)
  104. }
  105. }()
  106. fn(e)
  107. }
  108. // Stop shuts down the bus: the dispatch goroutine exits and any events still
  109. // buffered may be dropped. Handler goroutines already spawned for delivered
  110. // events run to completion on their own. Safe to call once.
  111. func (b *Bus) Stop() {
  112. close(b.done)
  113. b.wg.Wait()
  114. }