package shadowsocks import ( "context" "fmt" "sync" "time" "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" shadowsockslib "github.com/qdm12/ss-server/pkg/tcpudp" ) type Loop struct { state state // Other objects logger Logger // Internal channels and locks loopLock sync.Mutex running chan models.LoopStatus stop, stopped chan struct{} start chan struct{} backoffTime time.Duration runCancel context.CancelFunc runDone <-chan struct{} } const defaultBackoffTime = 10 * time.Second func NewLoop(settings settings.Shadowsocks, logger Logger) *Loop { return &Loop{ state: state{ status: constants.Stopped, settings: settings, }, logger: logger, start: make(chan struct{}), running: make(chan models.LoopStatus), stop: make(chan struct{}), stopped: make(chan struct{}), backoffTime: defaultBackoffTime, } } func (l *Loop) Start(ctx context.Context) (runError <-chan error, err error) { runCtx, runCancel := context.WithCancel(context.Background()) l.runCancel = runCancel ready := make(chan struct{}) done := make(chan struct{}) l.runDone = done go l.run(runCtx, ready, done) <-ready if *l.GetSettings().Enabled { _, err = l.SetStatus(ctx, constants.Running) if err != nil { return nil, fmt.Errorf("setting running status: %w", err) } } return nil, nil //nolint:nilnil } func (l *Loop) run(ctx context.Context, ready, done chan<- struct{}) { defer close(done) close(ready) select { case <-l.start: case <-ctx.Done(): return } crashed := false for ctx.Err() == nil { settings := l.GetSettings() server, err := shadowsockslib.NewServer(settings.Settings, l.logger) if err != nil { crashed = true l.logAndWait(ctx, err) continue } shadowsocksCtx, shadowsocksCancel := context.WithCancel(ctx) waitError := make(chan error) go func() { waitError <- server.Listen(shadowsocksCtx) }() if err != nil { crashed = true shadowsocksCancel() l.logAndWait(ctx, err) continue } isStableTimer := time.NewTimer(time.Second) stayHere := true for stayHere { select { case <-ctx.Done(): shadowsocksCancel() <-waitError close(waitError) return case <-isStableTimer.C: if !crashed { l.running <- constants.Running crashed = false } else { l.backoffTime = defaultBackoffTime l.state.setStatusWithLock(constants.Running) } case <-l.start: l.logger.Info("starting") shadowsocksCancel() <-waitError close(waitError) stayHere = false case <-l.stop: l.logger.Info("stopping") shadowsocksCancel() <-waitError close(waitError) l.stopped <- struct{}{} case err := <-waitError: // unexpected error shadowsocksCancel() close(waitError) if ctx.Err() != nil { return } l.state.setStatusWithLock(constants.Crashed) l.logAndWait(ctx, err) crashed = true stayHere = false } } shadowsocksCancel() // repetition for linter only isStableTimer.Stop() } } func (l *Loop) Stop() (err error) { l.runCancel() <-l.runDone return nil } func (l *Loop) logAndWait(ctx context.Context, err error) { if err != nil { l.logger.Error(err.Error()) } l.logger.Info("retrying in " + l.backoffTime.String()) timer := time.NewTimer(l.backoffTime) l.backoffTime *= 2 select { case <-timer.C: case <-ctx.Done(): if !timer.Stop() { <-timer.C } } }