notify.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package storage
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. )
  6. // Notification is a single NOTIFY event delivered to subscribers of a channel.
  7. // Payload is the application-supplied text (at most 8000 bytes), PID is the
  8. // backend that raised it, and LSN is the commit LSN of the transaction that
  9. // made it visible (zero for a NOTIFY outside a write transaction).
  10. type Notification struct {
  11. PID int32
  12. Channel string
  13. Payload string
  14. LSN uint64
  15. }
  16. // Subscriber is one connection's view of the notification hub. A subscriber
  17. // may listen to many channels and is registered with exactly one Hub.
  18. type Subscriber struct {
  19. id int32
  20. // notifications is bounded so a slow client can never block a publisher.
  21. // Hub.Publish drops the whole subscriber (closing overflow) instead of
  22. // waiting when the buffer is full.
  23. notifications chan Notification
  24. overflow chan struct{}
  25. overflowOnce sync.Once
  26. }
  27. // NewSubscriber creates a subscriber with the given backend PID and an
  28. // outbound queue of queueSize notifications.
  29. func NewSubscriber(pid int32, queueSize int) *Subscriber {
  30. if queueSize <= 0 {
  31. queueSize = defaultNotificationQueue
  32. }
  33. return &Subscriber{
  34. id: pid,
  35. notifications: make(chan Notification, queueSize),
  36. overflow: make(chan struct{}),
  37. }
  38. }
  39. const defaultNotificationQueue = 128
  40. // Notifications returns the channel notifications are delivered on.
  41. func (s *Subscriber) Notifications() <-chan Notification { return s.notifications }
  42. // Overflow is closed when the subscriber could not keep up and has been
  43. // removed from the hub. Protocol layers should disconnect the connection.
  44. func (s *Subscriber) Overflow() <-chan struct{} { return s.overflow }
  45. // PID returns the backend PID associated with the subscriber.
  46. func (s *Subscriber) PID() int32 { return s.id }
  47. func (s *Subscriber) markOverflow() {
  48. s.overflowOnce.Do(func() { close(s.overflow) })
  49. }
  50. // Hub is a per-database channel registry. It maps channel names to subscribed
  51. // connections and broadcasts notifications without blocking publishers. A Hub
  52. // is scoped to one DatabaseInstance, so tenants sharing a process never observe
  53. // each other's channels.
  54. type Hub struct {
  55. mu sync.RWMutex
  56. channels map[string]map[*Subscriber]struct{}
  57. subscribers map[*Subscriber]struct{}
  58. published atomic.Uint64
  59. dropped atomic.Uint64
  60. lastLSN atomic.Uint64
  61. }
  62. // NewHub creates an empty notification hub.
  63. func NewHub() *Hub {
  64. return &Hub{
  65. channels: make(map[string]map[*Subscriber]struct{}),
  66. subscribers: make(map[*Subscriber]struct{}),
  67. }
  68. }
  69. // Subscribe registers a subscriber for a channel. Duplicate subscriptions are
  70. // idempotent.
  71. func (h *Hub) Subscribe(s *Subscriber, channel string) {
  72. if h == nil || s == nil || channel == "" {
  73. return
  74. }
  75. h.mu.Lock()
  76. defer h.mu.Unlock()
  77. subs := h.channels[channel]
  78. if subs == nil {
  79. subs = make(map[*Subscriber]struct{})
  80. h.channels[channel] = subs
  81. }
  82. subs[s] = struct{}{}
  83. h.subscribers[s] = struct{}{}
  84. }
  85. // Unsubscribe removes a subscriber from one channel.
  86. func (h *Hub) Unsubscribe(s *Subscriber, channel string) {
  87. if h == nil || s == nil {
  88. return
  89. }
  90. h.mu.Lock()
  91. defer h.mu.Unlock()
  92. h.unsubscribeLocked(s, channel)
  93. }
  94. // UnsubscribeAll removes a subscriber from every channel. It is safe to call
  95. // more than once (disconnect cleanup may race an overflow removal).
  96. func (h *Hub) UnsubscribeAll(s *Subscriber) {
  97. if h == nil || s == nil {
  98. return
  99. }
  100. h.mu.Lock()
  101. defer h.mu.Unlock()
  102. for channel := range h.channels {
  103. h.unsubscribeLocked(s, channel)
  104. }
  105. delete(h.subscribers, s)
  106. }
  107. func (h *Hub) unsubscribeLocked(s *Subscriber, channel string) {
  108. subs := h.channels[channel]
  109. if subs == nil {
  110. return
  111. }
  112. delete(subs, s)
  113. if len(subs) == 0 {
  114. delete(h.channels, channel)
  115. }
  116. // The subscriber remains in h.subscribers if it still listens elsewhere.
  117. remaining := false
  118. for _, members := range h.channels {
  119. if _, ok := members[s]; ok {
  120. remaining = true
  121. break
  122. }
  123. }
  124. if !remaining {
  125. delete(h.subscribers, s)
  126. }
  127. }
  128. // Publish delivers a notification to every subscriber of its channel. It never
  129. // blocks: a subscriber whose bounded queue is full is removed and marked
  130. // overflowed so its protocol layer can disconnect it.
  131. func (h *Hub) Publish(n Notification) {
  132. if h == nil || n.Channel == "" {
  133. return
  134. }
  135. n.LSN = h.assignLSN(n.LSN)
  136. h.mu.RLock()
  137. targets := make([]*Subscriber, 0, len(h.channels[n.Channel]))
  138. for s := range h.channels[n.Channel] {
  139. targets = append(targets, s)
  140. }
  141. h.mu.RUnlock()
  142. if len(targets) == 0 {
  143. return
  144. }
  145. var overflowed []*Subscriber
  146. for _, s := range targets {
  147. select {
  148. case s.notifications <- n:
  149. h.published.Add(1)
  150. default:
  151. s.markOverflow()
  152. overflowed = append(overflowed, s)
  153. h.dropped.Add(1)
  154. }
  155. }
  156. for _, s := range overflowed {
  157. h.UnsubscribeAll(s)
  158. }
  159. }
  160. // assignLSN makes notification ordering monotonic within a database. A write
  161. // transaction supplies its storage LSN; a NOTIFY-only transaction (which has no
  162. // durable write) advances the hub's logical cursor instead of reporting zero.
  163. func (h *Hub) assignLSN(lsn uint64) uint64 {
  164. for {
  165. current := h.lastLSN.Load()
  166. next := current + 1
  167. if lsn > next {
  168. next = lsn
  169. }
  170. if h.lastLSN.CompareAndSwap(current, next) {
  171. return next
  172. }
  173. }
  174. }
  175. // Stats returns the number of published and dropped notifications. It is used
  176. // by metrics and tests.
  177. func (h *Hub) Stats() (published, dropped uint64) {
  178. if h == nil {
  179. return 0, 0
  180. }
  181. return h.published.Load(), h.dropped.Load()
  182. }
  183. // Channels returns the channels with at least one subscriber.
  184. func (h *Hub) Channels() []string {
  185. if h == nil {
  186. return nil
  187. }
  188. h.mu.RLock()
  189. defer h.mu.RUnlock()
  190. channels := make([]string, 0, len(h.channels))
  191. for channel := range h.channels {
  192. channels = append(channels, channel)
  193. }
  194. return channels
  195. }