srs/internal/signal/signal.go
Winlin 6ee6f1ca5f Proxy: Refactor for testability; add SRT/WHIP E2E and unit tests. v7.0.148 (#4675)
- Refactor the Go proxy for dependency injection: every proxy server,
the bootstrap, the signal handler, the load balancers, and AMF0 now accept
functional-option seams (factories/closures) so tests can inject fakes
without binding real sockets, talking to real Redis, or racing on
package globals.

- Drop the package-global `lb.SrsLoadBalancer`. The bootstrap creates
the LB locally and threads it through every proxy server constructor. Two old
global indirections in `internal/signal` and `internal/rtmp/amf0` are
likewise replaced by per-instance fields.

- Rename `internal/server` → `internal/proxy` and rename the `lb` public
surface for clarity: `SRSLoadBalancer` is split into `OriginService` /
`HLSService` / `RTCService` and recomposed as `OriginLoadBalancer`;
`SRSServer` → `OriginServer`; all proxy server types gain a `Proxy`
qualifier (e.g. `RTMPServer` → `RTMPProxyServer`).

- Extract the Redis client behind a new `internal/redisclient` package
with a minimal `RedisClient` interface and a counterfeiter fake.

- Add counterfeiter fakes (`proxyfakes`, `lbfakes`, `redisclientfakes`)
and ~7.5k lines of unit tests covering bootstrap, memory + Redis LBs, all
five proxy servers, the signal handler, and AMF0.

- Add two new E2E flows — `proxy-e2e-srt-test.sh` (SRT publish through
proxy, verify SRT/RTMP/HTTP-FLV/HLS playback) and `proxy-e2e-whip-test.sh`
(WHIP publish, verify RTMP/HTTP-FLV/HLS via origin `rtc_to_rtmp`) — plus
`setup-ffmpeg-with-whip.sh`, a macOS builder for an ffmpeg with
openssl-DTLS WHIP and SRT support that the two scripts auto-invoke when needed.

- Workspace reorg: move `memory/` and `skills/` to the repo root so all
agent tools (Claude / Codex / Kiro / OpenClaw) share one source of truth via
symlinks. Sync `docs/proxy/proxy-load-balancer.md` and
`memory/srs-codebase-map.md` with the new names.

No protocol, log, HTTP API, or wire-format changes. Refactor only — all
  externally observable proxy behavior is unchanged.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-05-17 12:09:07 -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
}