114 lines
2.4 KiB
Go
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 }
|