208 строки
5.1 KiB
Go
208 строки
5.1 KiB
Go
package synchronizer
|
|
|
|
import (
|
|
"errors"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/splitio/go-split-commons/v3/conf"
|
|
"github.com/splitio/go-split-commons/v3/push"
|
|
"github.com/splitio/go-split-commons/v3/service"
|
|
"github.com/splitio/go-split-commons/v3/storage"
|
|
"github.com/splitio/go-toolkit/v4/backoff"
|
|
"github.com/splitio/go-toolkit/v4/logging"
|
|
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
|
)
|
|
|
|
const (
|
|
// Ready represents ready
|
|
Ready = iota
|
|
// StreamingReady ready
|
|
StreamingReady
|
|
// Error represents some error in SSE streaming
|
|
Error
|
|
)
|
|
|
|
// Operation mode constants
|
|
const (
|
|
Streaming = iota
|
|
Polling
|
|
)
|
|
|
|
// Manager interface
|
|
type Manager interface {
|
|
Start()
|
|
Stop()
|
|
IsRunning() bool
|
|
}
|
|
|
|
// ManagerImpl struct
|
|
type ManagerImpl struct {
|
|
synchronizer Synchronizer
|
|
logger logging.LoggerInterface
|
|
config conf.AdvancedConfig
|
|
pushManager push.Manager
|
|
managerStatus chan int
|
|
streamingStatus chan int64
|
|
operationMode int32
|
|
lifecycle lifecycle.Manager
|
|
backoff backoff.Interface
|
|
}
|
|
|
|
// NewSynchronizerManager creates new sync manager
|
|
func NewSynchronizerManager(
|
|
synchronizer Synchronizer,
|
|
logger logging.LoggerInterface,
|
|
config conf.AdvancedConfig,
|
|
authClient service.AuthClient,
|
|
splitStorage storage.SplitStorage,
|
|
managerStatus chan int,
|
|
) (*ManagerImpl, error) {
|
|
if managerStatus == nil || cap(managerStatus) < 1 {
|
|
return nil, errors.New("Status channel cannot be nil nor having capacity")
|
|
}
|
|
|
|
manager := &ManagerImpl{
|
|
backoff: backoff.New(),
|
|
synchronizer: synchronizer,
|
|
logger: logger,
|
|
config: config,
|
|
managerStatus: managerStatus,
|
|
}
|
|
manager.lifecycle.Setup()
|
|
if config.StreamingEnabled {
|
|
streamingStatus := make(chan int64, 1000)
|
|
pushManager, err := push.NewManager(logger, synchronizer, &config, streamingStatus, authClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
manager.pushManager = pushManager
|
|
manager.streamingStatus = streamingStatus
|
|
}
|
|
|
|
return manager, nil
|
|
}
|
|
|
|
// IsRunning returns true if is in Streaming or Polling
|
|
func (s *ManagerImpl) IsRunning() bool {
|
|
return s.lifecycle.IsRunning()
|
|
}
|
|
|
|
// Start starts synchronization through Split
|
|
func (s *ManagerImpl) Start() {
|
|
if !s.lifecycle.BeginInitialization() {
|
|
s.logger.Info("Manager is already running, skipping start")
|
|
return
|
|
}
|
|
|
|
// It's safe to drain the channel here, since it's guaranteed that the manager status is "starting"
|
|
// push manager is still stopped
|
|
for len(s.managerStatus) > 0 {
|
|
<-s.managerStatus
|
|
}
|
|
err := s.synchronizer.SyncAll(false)
|
|
if err != nil {
|
|
defer s.lifecycle.ShutdownComplete()
|
|
s.managerStatus <- Error
|
|
return
|
|
}
|
|
|
|
if !s.lifecycle.InitializationComplete() {
|
|
defer s.lifecycle.ShutdownComplete()
|
|
return
|
|
}
|
|
s.logger.Debug("SyncAll Ready")
|
|
s.managerStatus <- Ready
|
|
s.synchronizer.StartPeriodicDataRecording()
|
|
|
|
if !s.config.StreamingEnabled {
|
|
s.logger.Info("SDK initialized in polling mode")
|
|
s.startPolling()
|
|
go func() { // create a goroutine that stops everything (the same way the streaming status watcher would)
|
|
<-s.lifecycle.ShutdownRequested()
|
|
s.stop()
|
|
}()
|
|
return
|
|
}
|
|
|
|
// Start streaming
|
|
s.logger.Info("SDK Initialized in streaming mode")
|
|
s.pushManager.Start()
|
|
go s.pushStatusWatcher()
|
|
}
|
|
|
|
func (s *ManagerImpl) stop() {
|
|
if s.pushManager != nil {
|
|
s.pushManager.Stop()
|
|
}
|
|
s.synchronizer.StopPeriodicFetching()
|
|
s.synchronizer.StopPeriodicDataRecording()
|
|
s.lifecycle.ShutdownComplete()
|
|
}
|
|
|
|
// Stop stop synchronizaation through Split
|
|
func (s *ManagerImpl) Stop() {
|
|
if !s.lifecycle.BeginShutdown() {
|
|
s.logger.Info("sync manager not yet running, skipping shutdown.")
|
|
return
|
|
}
|
|
|
|
s.logger.Info("Stopping all synchronization tasks")
|
|
s.lifecycle.AwaitShutdownComplete()
|
|
}
|
|
|
|
func (s *ManagerImpl) pushStatusWatcher() {
|
|
defer s.stop()
|
|
for {
|
|
select {
|
|
case <-s.lifecycle.ShutdownRequested():
|
|
return
|
|
case status := <-s.streamingStatus:
|
|
switch status {
|
|
case push.StatusUp:
|
|
s.stopPolling()
|
|
s.logger.Info("streaming up and running")
|
|
s.enableStreaming()
|
|
s.synchronizer.SyncAll(true)
|
|
case push.StatusDown:
|
|
s.logger.Info("streaming down, switchin to polling")
|
|
s.synchronizer.SyncAll(false)
|
|
s.pauseStreaming()
|
|
s.startPolling()
|
|
case push.StatusRetryableError:
|
|
howLong := s.backoff.Next()
|
|
s.logger.Error("retryable error in streaming subsystem. Switching to polling and retrying in ", howLong, " seconds")
|
|
s.pushManager.Stop()
|
|
s.synchronizer.SyncAll(false)
|
|
s.startPolling()
|
|
time.Sleep(howLong)
|
|
s.pushManager.Start()
|
|
case push.StatusNonRetryableError:
|
|
s.logger.Error("non retryable error in streaming subsystem. Switching to polling until next SDK initialization")
|
|
s.pushManager.Stop()
|
|
s.synchronizer.SyncAll(false)
|
|
s.startPolling()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *ManagerImpl) startPolling() {
|
|
atomic.StoreInt32(&s.operationMode, Polling)
|
|
s.synchronizer.StartPeriodicFetching()
|
|
}
|
|
|
|
func (s *ManagerImpl) stopPolling() {
|
|
s.synchronizer.StopPeriodicFetching()
|
|
}
|
|
|
|
func (s *ManagerImpl) pauseStreaming() {
|
|
s.pushManager.StartWorkers()
|
|
}
|
|
|
|
func (s *ManagerImpl) enableStreaming() {
|
|
s.pushManager.StartWorkers()
|
|
atomic.StoreInt32(&s.operationMode, Streaming)
|
|
s.backoff.Reset()
|
|
}
|