- diffConfig reports the env var names (from new struct tags) instead of Go field names, so the reload-env reply names what the user can change - the monitor's GPU line now shows the game detector's last finding (external holders or none) and how long ago the check ran - control channel: 10s per-connection watchdog (abortive force-close), cap of 32 concurrent connections, reload-env rate-limited; command read was already capped at 4 KiB
67 lines
1.5 KiB
Go
67 lines
1.5 KiB
Go
//go:build linux
|
|
|
|
package control
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// sockPath lives in the unit's RuntimeDirectory; mode 0666 lets every
|
|
// local user ask, nothing can reach it from off the machine.
|
|
const sockPath = "/run/gpu-turnstile/control.sock"
|
|
|
|
// Serve starts the socket listener in the background and returns; only a
|
|
// setup failure is reported. Each client connection is answered in its own
|
|
// goroutine.
|
|
func Serve(ctx context.Context, h Handler, log *slog.Logger) error {
|
|
os.Remove(sockPath) // stale socket from a previous run
|
|
ln, err := net.Listen("unix", sockPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Chmod(sockPath, 0o666); err != nil {
|
|
ln.Close()
|
|
return err
|
|
}
|
|
go func() {
|
|
<-ctx.Done()
|
|
ln.Close()
|
|
}()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return // shutting down
|
|
}
|
|
uc := unixConn{c}
|
|
if !serve(uc, h) {
|
|
uc.ForceClose()
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// unixConn adds an abortive ForceClose to net.Conn: a deadline in the
|
|
// past fails pending and future I/O immediately.
|
|
type unixConn struct{ net.Conn }
|
|
|
|
func (c unixConn) ForceClose() error {
|
|
c.SetDeadline(time.Now().Add(-time.Second)) //nolint:errcheck // best effort
|
|
return c.Conn.Close()
|
|
}
|
|
|
|
// Ask sends one command to the running service and returns its reply.
|
|
func Ask(cmd string) (string, error) {
|
|
c, err := net.DialTimeout("unix", sockPath, 2*time.Second)
|
|
if err != nil {
|
|
return "", ErrUnavailable
|
|
}
|
|
defer c.Close()
|
|
return readReply(c, cmd)
|
|
}
|