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  }