github.com/anycable/anycable-go@v1.5.1/broker/broker.go (about) 1 package broker 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "time" 8 9 "github.com/anycable/anycable-go/common" 10 ) 11 12 // Broadcaster is responsible for fanning-out messages to the stream clients 13 // and other nodes 14 type Broadcaster interface { 15 Broadcast(msg *common.StreamMessage) 16 BroadcastCommand(msg *common.RemoteCommandMessage) 17 Subscribe(stream string) 18 Unsubscribe(stream string) 19 } 20 21 // Cacheable is an interface which a session object must implement 22 // to be stored in cache. 23 // We use interface and not require a string cache entry to be passed to avoid 24 // unnecessary dumping when broker doesn't support storing sessions. 25 type Cacheable interface { 26 ToCacheEntry() ([]byte, error) 27 } 28 29 // Broker is responsible for: 30 // - Managing streams history. 31 // - Keeping client states for recovery. 32 // - Distributing broadcasts across nodes. 33 // 34 //go:generate mockery --name Broker --output "../mocks" --outpkg mocks 35 type Broker interface { 36 Start(done chan (error)) error 37 Shutdown(ctx context.Context) error 38 39 Announce() string 40 41 HandleBroadcast(msg *common.StreamMessage) 42 HandleCommand(msg *common.RemoteCommandMessage) 43 44 // Registers the stream and returns its (short) unique identifier 45 Subscribe(stream string) string 46 // (Maybe) unregisters the stream and return its unique identifier 47 Unsubscribe(stream string) string 48 // Retrieves stream messages from history from the specified offset within the specified epoch 49 HistoryFrom(stream string, epoch string, offset uint64) ([]common.StreamMessage, error) 50 // Retrieves stream messages from history from the specified timestamp 51 HistorySince(stream string, ts int64) ([]common.StreamMessage, error) 52 53 // Saves session's state in cache 54 CommitSession(sid string, session Cacheable) error 55 // Fetches session's state from cache (by session id) 56 RestoreSession(from string) ([]byte, error) 57 // Marks session as finished (for cache expiration) 58 FinishSession(sid string) error 59 } 60 61 // LocalBroker is a single-node broker that can used to store streams data locally 62 type LocalBroker interface { 63 Start(done chan (error)) error 64 Shutdown(ctx context.Context) error 65 SetEpoch(epoch string) 66 GetEpoch() string 67 HistoryFrom(stream string, epoch string, offset uint64) ([]common.StreamMessage, error) 68 HistorySince(stream string, ts int64) ([]common.StreamMessage, error) 69 Store(stream string, msg []byte, seq uint64, ts time.Time) (uint64, error) 70 } 71 72 type StreamsTracker struct { 73 store map[string]uint64 74 75 mu sync.Mutex 76 } 77 78 func NewStreamsTracker() *StreamsTracker { 79 return &StreamsTracker{store: make(map[string]uint64)} 80 } 81 82 func (s *StreamsTracker) Add(name string) (isNew bool) { 83 s.mu.Lock() 84 defer s.mu.Unlock() 85 if _, ok := s.store[name]; !ok { 86 s.store[name] = 1 87 return true 88 } 89 90 s.store[name]++ 91 return false 92 } 93 94 func (s *StreamsTracker) Has(name string) bool { 95 s.mu.Lock() 96 defer s.mu.Unlock() 97 98 _, ok := s.store[name] 99 100 return ok 101 } 102 103 func (s *StreamsTracker) Remove(name string) (isLast bool) { 104 s.mu.Lock() 105 defer s.mu.Unlock() 106 if _, ok := s.store[name]; !ok { 107 return false 108 } 109 110 if s.store[name] == 1 { 111 delete(s.store, name) 112 return true 113 } 114 115 return false 116 } 117 118 // LegacyBroker preserves the v1 behaviour while implementing the Broker APIs. 119 // Thus, we can use it without breaking the older behaviour 120 type LegacyBroker struct { 121 broadcaster Broadcaster 122 tracker *StreamsTracker 123 } 124 125 var _ Broker = (*LegacyBroker)(nil) 126 127 func NewLegacyBroker(broadcaster Broadcaster) *LegacyBroker { 128 return &LegacyBroker{ 129 broadcaster: broadcaster, 130 tracker: NewStreamsTracker(), 131 } 132 } 133 134 func (LegacyBroker) Start(done chan (error)) error { 135 return nil 136 } 137 138 func (LegacyBroker) Shutdown(ctx context.Context) error { 139 return nil 140 } 141 142 func (LegacyBroker) Announce() string { 143 return "Using no-op (legacy) broker" 144 } 145 146 func (b *LegacyBroker) HandleBroadcast(msg *common.StreamMessage) { 147 if b.tracker.Has(msg.Stream) { 148 b.broadcaster.Broadcast(msg) 149 } 150 } 151 152 func (b *LegacyBroker) HandleCommand(msg *common.RemoteCommandMessage) { 153 b.broadcaster.BroadcastCommand(msg) 154 } 155 156 // Registring streams (for granular pub/sub) 157 func (b *LegacyBroker) Subscribe(stream string) string { 158 isNew := b.tracker.Add(stream) 159 160 if isNew { 161 b.broadcaster.Subscribe(stream) 162 } 163 164 return stream 165 } 166 167 func (b *LegacyBroker) Unsubscribe(stream string) string { 168 isLast := b.tracker.Remove(stream) 169 170 if isLast { 171 b.broadcaster.Unsubscribe(stream) 172 } 173 174 return stream 175 } 176 177 func (LegacyBroker) HistoryFrom(stream string, epoch string, offset uint64) ([]common.StreamMessage, error) { 178 return nil, errors.New("history not supported") 179 } 180 181 func (LegacyBroker) HistorySince(stream string, ts int64) ([]common.StreamMessage, error) { 182 return nil, errors.New("history not supported") 183 } 184 185 func (LegacyBroker) CommitSession(sid string, session Cacheable) error { 186 return nil 187 } 188 189 func (LegacyBroker) RestoreSession(from string) ([]byte, error) { 190 return nil, nil 191 } 192 193 func (LegacyBroker) FinishSession(sid string) error { 194 return nil 195 }