Add killsignal and killtimeout to exec/rtsp
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
package exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// closer support custom killsignal with custom killtimeout
|
||||||
|
type closer struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
query url.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closer) Close() (err error) {
|
||||||
|
sig := os.Kill
|
||||||
|
if s := c.query.Get("killsignal"); s != "" {
|
||||||
|
sig = syscall.Signal(core.Atoi(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().Msgf("[exec] kill with signal=%d", sig)
|
||||||
|
err = c.cmd.Process.Signal(sig)
|
||||||
|
|
||||||
|
if s := c.query.Get("killtimeout"); s != "" {
|
||||||
|
timeout := time.Duration(core.Atoi(s)) * time.Second
|
||||||
|
timer := time.AfterFunc(timeout, func() {
|
||||||
|
log.Trace().Msgf("[exec] kill after timeout=%s", s)
|
||||||
|
_ = c.cmd.Process.Kill()
|
||||||
|
})
|
||||||
|
defer timer.Stop() // stop timer if Wait ends before timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(err, c.cmd.Wait())
|
||||||
|
}
|
||||||
+31
-22
@@ -1,10 +1,12 @@
|
|||||||
package exec
|
package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -49,8 +51,10 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func execHandle(rawURL string) (core.Producer, error) {
|
func execHandle(rawURL string) (core.Producer, error) {
|
||||||
|
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
|
||||||
|
query := streams.ParseQuery(rawQuery)
|
||||||
|
|
||||||
var path string
|
var path string
|
||||||
var query url.Values
|
|
||||||
|
|
||||||
// RTSP flow should have `{output}` inside URL
|
// RTSP flow should have `{output}` inside URL
|
||||||
// pipe flow may have `#{params}` inside URL
|
// pipe flow may have `#{params}` inside URL
|
||||||
@@ -62,9 +66,6 @@ func execHandle(rawURL string) (core.Producer, error) {
|
|||||||
sum := md5.Sum([]byte(rawURL))
|
sum := md5.Sum([]byte(rawURL))
|
||||||
path = "/" + hex.EncodeToString(sum[:])
|
path = "/" + hex.EncodeToString(sum[:])
|
||||||
rawURL = rawURL[:i] + "rtsp://127.0.0.1:" + rtsp.Port + path + rawURL[i+8:]
|
rawURL = rawURL[:i] + "rtsp://127.0.0.1:" + rtsp.Port + path + rawURL[i+8:]
|
||||||
} else if i = strings.IndexByte(rawURL, '#'); i > 0 {
|
|
||||||
query = streams.ParseQuery(rawURL[i+1:])
|
|
||||||
rawURL = rawURL[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := shell.QuoteSplit(rawURL[5:]) // remove `exec:`
|
args := shell.QuoteSplit(rawURL[5:]) // remove `exec:`
|
||||||
@@ -74,23 +75,34 @@ func execHandle(rawURL string) (core.Producer, error) {
|
|||||||
debug: log.Debug().Enabled(),
|
debug: log.Debug().Enabled(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
|
||||||
return handlePipe(rawURL, cmd, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleRTSP(rawURL, cmd, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePipe(source string, cmd *exec.Cmd, query url.Values) (core.Producer, error) {
|
|
||||||
if query.Get("backchannel") == "1" {
|
if query.Get("backchannel") == "1" {
|
||||||
return stdin.NewClient(cmd)
|
return stdin.NewClient(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := PipeCloser(cmd, query)
|
cl := &closer{cmd: cmd, query: query}
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
return handlePipe(rawURL, cmd, cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleRTSP(rawURL, cmd, cl, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePipe(source string, cmd *exec.Cmd, cl io.Closer) (core.Producer, error) {
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc := struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{
|
||||||
|
// add buffer for pipe reader to reduce syscall
|
||||||
|
bufio.NewReaderSize(stdout, core.BufferSize),
|
||||||
|
cl,
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug().Strs("args", cmd.Args).Msg("[exec] run pipe")
|
log.Debug().Strs("args", cmd.Args).Msg("[exec] run pipe")
|
||||||
|
|
||||||
ts := time.Now()
|
ts := time.Now()
|
||||||
@@ -99,9 +111,9 @@ func handlePipe(source string, cmd *exec.Cmd, query url.Values) (core.Producer,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prod, err := magic.Open(r)
|
prod, err := magic.Open(rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = r.Close()
|
_ = rc.Close()
|
||||||
return nil, fmt.Errorf("exec/pipe: %w\n%s", err, cmd.Stderr)
|
return nil, fmt.Errorf("exec/pipe: %w\n%s", err, cmd.Stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +127,7 @@ func handlePipe(source string, cmd *exec.Cmd, query url.Values) (core.Producer,
|
|||||||
return prod, nil
|
return prod, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRTSP(source string, cmd *exec.Cmd, path string) (core.Producer, error) {
|
func handleRTSP(source string, cmd *exec.Cmd, cl io.Closer, path string) (core.Producer, error) {
|
||||||
if log.Trace().Enabled() {
|
if log.Trace().Enabled() {
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
}
|
}
|
||||||
@@ -147,9 +159,9 @@ func handleRTSP(source string, cmd *exec.Cmd, path string) (core.Producer, error
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 60):
|
case <-time.After(time.Minute):
|
||||||
_ = cmd.Process.Kill()
|
|
||||||
log.Error().Str("source", source).Msg("[exec] timeout")
|
log.Error().Str("source", source).Msg("[exec] timeout")
|
||||||
|
_ = cl.Close()
|
||||||
return nil, errors.New("exec: timeout")
|
return nil, errors.New("exec: timeout")
|
||||||
case <-done:
|
case <-done:
|
||||||
// limit message size
|
// limit message size
|
||||||
@@ -157,10 +169,7 @@ func handleRTSP(source string, cmd *exec.Cmd, path string) (core.Producer, error
|
|||||||
case prod := <-waiter:
|
case prod := <-waiter:
|
||||||
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run rtsp")
|
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run rtsp")
|
||||||
setRemoteInfo(prod, source, cmd.Args)
|
setRemoteInfo(prod, source, cmd.Args)
|
||||||
prod.OnClose = func() error {
|
prod.OnClose = cl.Close
|
||||||
log.Debug().Msgf("[exec] kill rtsp")
|
|
||||||
return errors.Join(cmd.Process.Kill(), cmd.Wait())
|
|
||||||
}
|
|
||||||
return prod, nil
|
return prod, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package exec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PipeCloser - return StdoutPipe that Kill cmd on Close call
|
|
||||||
func PipeCloser(cmd *exec.Cmd, query url.Values) (io.ReadCloser, error) {
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add buffer for pipe reader to reduce syscall
|
|
||||||
return &pipeCloser{bufio.NewReaderSize(stdout, core.BufferSize), stdout, cmd, query}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipeCloser struct {
|
|
||||||
io.Reader
|
|
||||||
io.Closer
|
|
||||||
cmd *exec.Cmd
|
|
||||||
query url.Values
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipeCloser) Close() error {
|
|
||||||
return errors.Join(p.Closer.Close(), p.Kill(), p.Wait())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipeCloser) Kill() error {
|
|
||||||
if s := p.query.Get("killsignal"); s != "" {
|
|
||||||
log.Trace().Msgf("[exec] kill with custom sig=%s", s)
|
|
||||||
sig := syscall.Signal(core.Atoi(s))
|
|
||||||
return p.cmd.Process.Signal(sig)
|
|
||||||
}
|
|
||||||
return p.cmd.Process.Kill()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipeCloser) Wait() error {
|
|
||||||
if s := p.query.Get("killtimeout"); s != "" {
|
|
||||||
timeout := time.Duration(core.Atoi(s)) * time.Second
|
|
||||||
timer := time.AfterFunc(timeout, func() {
|
|
||||||
log.Trace().Msgf("[exec] kill after timeout=%s", s)
|
|
||||||
_ = p.cmd.Process.Kill()
|
|
||||||
})
|
|
||||||
defer timer.Stop() // stop timer if Wait ends before timeout
|
|
||||||
}
|
|
||||||
return p.cmd.Wait()
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user