srs/internal/signal/signal.go
winlin 3060bf8e7c Claude: Split lb interfaces, extract redisclient, drop race-prone globals.
- Split OriginLoadBalancer into OriginService / HLSService / RTCService;
  the original interface now embeds the three role interfaces. Generate
  counterfeiter fakes for all four.
- Extract internal/redisclient: RedisClient interface + New() factory.
  internal/lb/redis.go no longer imports github.com/go-redis/redis/v8.
- Add unit tests for lb.go (OriginServer.ID/String/Format/NewOriginServer)
  and for the full memory + redis load balancers.
- Replace package-level test seams (memoryKeepaliveInterval, newRedisClient,
  redisKeepaliveInterval, signal.signalNotify/osExit, rtmp.createBuffer) with
  per-instance struct fields so concurrent tests can't race on them.
- Promote signal.InstallSignals / InstallForceQuit onto a new signal.Handler
  type; update bootstrap to construct one.
- Move rtmp createBuffer onto amf0ObjectBase as bufFactory; the three AMF0
  marshalers and their tests use the per-instance factory.
- Make proxy test scripts locate the workspace by walking up to go.mod
  instead of brittle '../../../..' counting (symlink-aware).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:11:18 -04:00

64 lines
1.4 KiB
Go

// Copyright (c) 2026 Winlin
//
// SPDX-License-Identifier: MIT
package signal
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"srsx/internal/env"
"srsx/internal/errors"
"srsx/internal/logger"
)
// Handler installs OS signal handlers and the force-quit timer. The notify
// and exit indirections are struct fields (not package globals) so concurrent
// tests can each construct a handler with their own fakes without racing on
// shared state.
type Handler struct {
notify func(c chan<- os.Signal, sig ...os.Signal)
exit func(code int)
}
// NewHandler returns a Handler wired to the real OS implementations.
func NewHandler() *Handler {
return &Handler{
notify: signal.Notify,
exit: os.Exit,
}
}
func (h *Handler) InstallSignals(ctx context.Context, cancel context.CancelFunc) {
sc := make(chan os.Signal, 1)
h.notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
go func() {
for s := range sc {
logger.Debug(ctx, "Got signal %v", s)
cancel()
}
}()
}
func (h *Handler) InstallForceQuit(ctx context.Context, environment env.ProxyEnvironment) error {
var forceTimeout time.Duration
timeoutStr := environment.ForceQuitTimeout()
if t, err := time.ParseDuration(timeoutStr); err != nil {
return errors.Wrapf(err, "parse force timeout %v", timeoutStr)
} else {
forceTimeout = t
}
go func() {
<-ctx.Done()
time.Sleep(forceTimeout)
logger.Warn(ctx, "Force to exit by timeout")
h.exit(1)
}()
return nil
}