github.com/anycable/anycable-go@v1.5.1/broker/memory.go (about) 1 package broker 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/anycable/anycable-go/common" 11 nanoid "github.com/matoous/go-nanoid" 12 ) 13 14 type entry struct { 15 timestamp int64 16 offset uint64 17 data string 18 } 19 20 type memstream struct { 21 offset uint64 22 deadline int64 23 // The lowest available offset in the stream 24 low uint64 25 data []*entry 26 ttl int64 27 limit int 28 29 mu sync.RWMutex 30 } 31 32 func (ms *memstream) add(data string) uint64 { 33 ms.mu.Lock() 34 defer ms.mu.Unlock() 35 36 ts := time.Now().Unix() 37 38 ms.offset++ 39 40 entry := &entry{ 41 offset: ms.offset, 42 timestamp: ts, 43 data: data, 44 } 45 46 ms.appendEntry(entry) 47 48 return ms.offset 49 } 50 51 func (ms *memstream) insert(data string, offset uint64, t time.Time) (uint64, error) { 52 ms.mu.Lock() 53 defer ms.mu.Unlock() 54 55 if t == (time.Time{}) { 56 t = time.Now() 57 } 58 59 ts := t.Unix() 60 61 if ms.offset >= offset { 62 return 0, fmt.Errorf("offset %d is already taken", offset) 63 } 64 65 ms.offset = offset 66 67 entry := &entry{ 68 offset: offset, 69 timestamp: ts, 70 data: data, 71 } 72 73 ms.appendEntry(entry) 74 75 return ms.offset, nil 76 } 77 78 func (ms *memstream) appendEntry(entry *entry) { 79 ms.data = append(ms.data, entry) 80 81 if len(ms.data) > ms.limit { 82 ms.data = ms.data[1:] 83 ms.low = ms.data[0].offset 84 } 85 86 if ms.low == 0 { 87 ms.low = ms.data[0].offset 88 } 89 90 // Update memstream expiration deadline on every added item 91 // We keep memstream alive for 10 times longer than ttl (so we can re-use it and its offset) 92 ms.deadline = time.Now().Add(time.Duration(ms.ttl*10) * time.Second).Unix() 93 } 94 95 func (ms *memstream) expire() { 96 ms.mu.Lock() 97 defer ms.mu.Unlock() 98 99 cutIndex := 0 100 101 now := time.Now().Unix() 102 deadline := now - ms.ttl 103 104 for _, entry := range ms.data { 105 if entry.timestamp < deadline { 106 cutIndex++ 107 continue 108 } 109 110 break 111 } 112 113 if cutIndex < 0 { 114 return 115 } 116 117 ms.data = ms.data[cutIndex:] 118 119 if len(ms.data) > 0 { 120 ms.low = ms.data[0].offset 121 } else { 122 ms.low = 0 123 } 124 } 125 126 func (ms *memstream) filterByOffset(offset uint64, callback func(e *entry)) error { 127 ms.mu.RLock() 128 defer ms.mu.RUnlock() 129 130 if ms.low > offset { 131 return fmt.Errorf("requested offset couldn't be found: %d, lowest: %d", offset, ms.low) 132 } 133 134 if ms.low == 0 { 135 return fmt.Errorf("stream is empty") 136 } 137 138 start := (offset - ms.low) + 1 139 140 if start > uint64(len(ms.data)) { 141 return fmt.Errorf("requested offset couldn't be found: %d, latest: %d", offset, ms.data[len(ms.data)-1].offset) 142 } 143 144 for _, v := range ms.data[start:] { 145 callback(v) 146 } 147 148 return nil 149 } 150 151 func (ms *memstream) filterByTime(since int64, callback func(e *entry)) error { 152 ms.mu.RLock() 153 defer ms.mu.RUnlock() 154 155 for _, v := range ms.data { 156 if v.timestamp >= since { 157 callback(v) 158 } 159 } 160 161 return nil 162 } 163 164 type sessionEntry struct { 165 data []byte 166 } 167 168 type expireSessionEntry struct { 169 deadline int64 170 sid string 171 } 172 173 type Memory struct { 174 broadcaster Broadcaster 175 config *Config 176 tracker *StreamsTracker 177 epoch string 178 streams map[string]*memstream 179 sessions map[string]*sessionEntry 180 expireSessions []*expireSessionEntry 181 182 streamsMu sync.RWMutex 183 sessionsMu sync.RWMutex 184 epochMu sync.RWMutex 185 } 186 187 var _ Broker = (*Memory)(nil) 188 189 func NewMemoryBroker(node Broadcaster, config *Config) *Memory { 190 epoch, _ := nanoid.Nanoid(4) 191 192 return &Memory{ 193 broadcaster: node, 194 config: config, 195 tracker: NewStreamsTracker(), 196 streams: make(map[string]*memstream), 197 sessions: make(map[string]*sessionEntry), 198 epoch: epoch, 199 } 200 } 201 202 func (b *Memory) Announce() string { 203 return fmt.Sprintf( 204 "Using in-memory broker (epoch: %s, history limit: %d, history ttl: %ds, sessions ttl: %ds)", 205 b.GetEpoch(), 206 b.config.HistoryLimit, 207 b.config.HistoryTTL, 208 b.config.SessionsTTL, 209 ) 210 } 211 212 func (b *Memory) GetEpoch() string { 213 b.epochMu.RLock() 214 defer b.epochMu.RUnlock() 215 216 return b.epoch 217 } 218 219 func (b *Memory) SetEpoch(v string) { 220 b.epochMu.Lock() 221 defer b.epochMu.Unlock() 222 223 b.epoch = v 224 } 225 226 func (b *Memory) Start(done chan (error)) error { 227 go b.expireLoop() 228 229 return nil 230 } 231 232 func (b *Memory) Shutdown(ctx context.Context) error { 233 return nil 234 } 235 236 func (b *Memory) HandleBroadcast(msg *common.StreamMessage) { 237 if msg.Meta != nil && msg.Meta.Transient { 238 b.broadcaster.Broadcast(msg) 239 return 240 } 241 242 offset := b.add(msg.Stream, msg.Data) 243 244 msg.Epoch = b.GetEpoch() 245 msg.Offset = offset 246 247 b.broadcaster.Broadcast(msg) 248 } 249 250 func (b *Memory) HandleCommand(msg *common.RemoteCommandMessage) { 251 b.broadcaster.BroadcastCommand(msg) 252 } 253 254 // Registring streams (for granular pub/sub) 255 256 func (b *Memory) Subscribe(stream string) string { 257 isNew := b.tracker.Add(stream) 258 259 if isNew { 260 b.broadcaster.Subscribe(stream) 261 } 262 263 return stream 264 } 265 266 func (b *Memory) Unsubscribe(stream string) string { 267 isLast := b.tracker.Remove(stream) 268 269 if isLast { 270 b.broadcaster.Unsubscribe(stream) 271 } 272 273 return stream 274 } 275 276 func (b *Memory) HistoryFrom(name string, epoch string, offset uint64) ([]common.StreamMessage, error) { 277 bepoch := b.GetEpoch() 278 279 if bepoch != epoch { 280 return nil, fmt.Errorf("unknown epoch: %s, current: %s", epoch, bepoch) 281 } 282 283 stream := b.get(name) 284 285 if stream == nil { 286 return nil, errors.New("stream not found") 287 } 288 289 history := []common.StreamMessage{} 290 291 err := stream.filterByOffset(offset, func(entry *entry) { 292 history = append(history, common.StreamMessage{ 293 Stream: name, 294 Data: entry.data, 295 Offset: entry.offset, 296 Epoch: bepoch, 297 }) 298 }) 299 300 if err != nil { 301 return nil, err 302 } 303 304 return history, nil 305 } 306 307 func (b *Memory) HistorySince(name string, ts int64) ([]common.StreamMessage, error) { 308 stream := b.get(name) 309 310 if stream == nil { 311 return nil, nil 312 } 313 314 bepoch := b.GetEpoch() 315 history := []common.StreamMessage{} 316 317 err := stream.filterByTime(ts, func(entry *entry) { 318 history = append(history, common.StreamMessage{ 319 Stream: name, 320 Data: entry.data, 321 Offset: entry.offset, 322 Epoch: bepoch, 323 }) 324 }) 325 326 if err != nil { 327 return nil, err 328 } 329 330 return history, nil 331 } 332 333 func (b *Memory) Store(name string, data []byte, offset uint64, ts time.Time) (uint64, error) { 334 b.streamsMu.Lock() 335 336 if _, ok := b.streams[name]; !ok { 337 b.streams[name] = &memstream{ 338 data: []*entry{}, 339 ttl: b.config.HistoryTTL, 340 limit: b.config.HistoryLimit, 341 } 342 } 343 344 stream := b.streams[name] 345 346 b.streamsMu.Unlock() 347 348 return stream.insert(string(data), offset, ts) 349 } 350 351 func (b *Memory) CommitSession(sid string, session Cacheable) error { 352 b.sessionsMu.Lock() 353 defer b.sessionsMu.Unlock() 354 355 cached, err := session.ToCacheEntry() 356 357 if err != nil { 358 return err 359 } 360 361 b.sessions[sid] = &sessionEntry{data: cached} 362 363 return nil 364 } 365 366 func (b *Memory) RestoreSession(from string) ([]byte, error) { 367 b.sessionsMu.RLock() 368 defer b.sessionsMu.RUnlock() 369 370 if cached, ok := b.sessions[from]; ok { 371 return cached.data, nil 372 } 373 374 return nil, nil 375 } 376 377 func (b *Memory) FinishSession(sid string) error { 378 b.sessionsMu.Lock() 379 defer b.sessionsMu.Unlock() 380 381 if _, ok := b.sessions[sid]; ok { 382 b.expireSessions = append( 383 b.expireSessions, 384 &expireSessionEntry{sid: sid, deadline: time.Now().Unix() + b.config.SessionsTTL}, 385 ) 386 } 387 388 return nil 389 } 390 391 func (b *Memory) add(name string, data string) uint64 { 392 b.streamsMu.Lock() 393 394 if _, ok := b.streams[name]; !ok { 395 b.streams[name] = &memstream{ 396 data: []*entry{}, 397 ttl: b.config.HistoryTTL, 398 limit: b.config.HistoryLimit, 399 } 400 } 401 402 stream := b.streams[name] 403 404 b.streamsMu.Unlock() 405 406 return stream.add(data) 407 } 408 409 func (b *Memory) get(name string) *memstream { 410 b.streamsMu.RLock() 411 defer b.streamsMu.RUnlock() 412 413 return b.streams[name] 414 } 415 416 func (b *Memory) expireLoop() { 417 for { 418 time.Sleep(time.Second) 419 b.expire() 420 } 421 } 422 423 func (b *Memory) expire() { 424 b.streamsMu.Lock() 425 426 toDelete := []string{} 427 428 now := time.Now().Unix() 429 430 for name, stream := range b.streams { 431 stream.expire() 432 433 if stream.deadline < now { 434 toDelete = append(toDelete, name) 435 } 436 } 437 438 for _, name := range toDelete { 439 delete(b.streams, name) 440 } 441 442 b.streamsMu.Unlock() 443 444 b.sessionsMu.Lock() 445 446 i := 0 447 448 for _, expired := range b.expireSessions { 449 if expired.deadline < now { 450 delete(b.sessions, expired.sid) 451 i++ 452 continue 453 } 454 break 455 } 456 457 b.expireSessions = b.expireSessions[i:] 458 459 b.sessionsMu.Unlock() 460 }