Files
yovision/Sense/server/app/sense/adapters/mediamtx/supervisor.go
T

114 lines
2.4 KiB
Go

package mediamtx
import (
"context"
"fmt"
"os"
"os/exec"
"sync"
"time"
)
type ProcessState struct {
Running bool `json:"running"`
PID int `json:"pid,omitempty"`
Restarts int `json:"restarts"`
LastExit string `json:"last_exit,omitempty"`
}
type Process interface {
Start(context.Context) error
Stop(context.Context) error
State() ProcessState
}
type Supervisor struct {
binary string
config string
maxRestarts int
mu sync.Mutex
command *exec.Cmd
state ProcessState
}
func NewSupervisor(binary, config string, maxRestarts int) *Supervisor {
if maxRestarts < 0 {
maxRestarts = 0
}
return &Supervisor{binary: binary, config: config, maxRestarts: maxRestarts}
}
func (s *Supervisor) Start(ctx context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.state.Running {
return nil
}
if s.binary == "" {
return fmt.Errorf("SENSE_MEDIAMTX_BINARY is not configured")
}
if s.state.LastExit != "" {
if s.state.Restarts >= s.maxRestarts {
return fmt.Errorf("MediaMTX restart limit reached")
}
s.state.Restarts++
}
arguments := []string{}
if s.config != "" {
arguments = append(arguments, s.config)
}
command := exec.CommandContext(context.Background(), s.binary, arguments...)
if err := command.Start(); err != nil {
return fmt.Errorf("start MediaMTX: %w", err)
}
s.command = command
s.state.Running = true
s.state.PID = command.Process.Pid
s.state.LastExit = ""
go s.wait(command)
return nil
}
func (s *Supervisor) wait(command *exec.Cmd) {
err := command.Wait()
s.mu.Lock()
defer s.mu.Unlock()
if s.command != command {
return
}
s.state.Running = false
s.state.PID = 0
if err != nil {
s.state.LastExit = err.Error()
} else {
s.state.LastExit = "exited"
}
}
func (s *Supervisor) Stop(ctx context.Context) error {
s.mu.Lock()
command := s.command
if command == nil || !s.state.Running {
s.mu.Unlock()
return nil
}
process := command.Process
s.mu.Unlock()
if err := process.Signal(os.Interrupt); err != nil {
if killErr := process.Kill(); killErr != nil {
return fmt.Errorf("stop owned MediaMTX process: %w", killErr)
}
}
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
s.mu.Lock()
running := s.state.Running
s.mu.Unlock()
if !running {
return nil
}
}
}
}
func (s *Supervisor) State() ProcessState { s.mu.Lock(); defer s.mu.Unlock(); return s.state }