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  }