limit run command to single instance

feat/muzan
Evan Chen 2021-11-11 09:58:36 +08:00
parent 091c026a46
commit eddd813846
3 changed files with 64 additions and 12 deletions

View File

@ -36,12 +36,15 @@ var Ext2Mode map[string]string = map[string]string{
type Action struct {
Name string `json:"name"`
Cmd string `json:"cmd"`
run chan struct{} `json:"-"`
pid int `json:"-"`
}
type Integration struct {
Name string `json:"name"`
Description string `json:"description"`
Cmd func() (string, error) `json:"-"`
run chan struct{} `json:"-"`
}
type ConfigUI struct {

32
file.go
View File

@ -1,7 +1,9 @@
package configui
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"runtime"
@ -25,6 +27,8 @@ type File struct {
lock sync.RWMutex `json:"-"`
owner *ConfigUI `json:"-"`
run chan struct{} `json:"-"`
pid int `json:"-"`
}
func (f *File) Read() ([]byte, error) {
@ -57,6 +61,20 @@ func (f *File) Do(CmdTimeout time.Duration) (string, error) {
}
f.lock.RLock()
defer f.lock.RUnlock()
// limit running instance to 1
if cap(f.run) == 0 {
f.run = make(chan struct{}, 1)
}
select {
case f.run <- struct{}{}:
defer func() { <-f.run }()
default:
f.owner.log.Error("task is running: ", f.Name)
return "", fmt.Errorf("another task of %s is running with pid: %d", f.Name, f.pid)
}
// prepare cmd
cmd := &exec.Cmd{}
if runtime.GOOS == "windows" {
cmd = exec.Command(WIN_SHELL, "/c", f.Action)
@ -66,12 +84,14 @@ func (f *File) Do(CmdTimeout time.Duration) (string, error) {
f.owner.log.Info("DO: ", f.Action)
done := make(chan string, 1)
go func() {
out, _ := cmd.CombinedOutput()
// real cmd err is unhandled, but passed to client
// if err != nil {
// return string(out), err
// }
done <- string(out)
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
f.pid = cmd.Process.Pid
cmd.Wait()
done <- b.String()
}()
select {
case <-time.After(CmdTimeout):

View File

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strconv"
"time"
"kumoly.io/lib/ksrv"
)
@ -130,9 +131,25 @@ func (cui *ConfigUI) Apply(w http.ResponseWriter, r *http.Request) {
func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
for _, v := range cui.Actions {
for i, v := range cui.Actions {
if v.Name == name {
// limit running instance to one
if cap(cui.Actions[i].run) != 1 {
cui.Actions[i].run = make(chan struct{}, 1)
}
select {
case cui.Actions[i].run <- struct{}{}:
defer func() { <-cui.Actions[i].run }()
default:
panic(fmt.Errorf("another task of %s is running with pid: %d", name, v.pid))
}
file := &File{Name: name, Action: v.Cmd, owner: cui}
go func() {
<-time.After(time.Millisecond * 10)
cui.Actions[i].pid = file.pid
}()
result, err := file.Do(cui.cmdTimeout)
if err != nil {
panic(err)
@ -146,8 +163,20 @@ func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
func (cui *ConfigUI) DoIntegration(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
for _, v := range cui.Integrations {
for i, v := range cui.Integrations {
if v.Name == name {
// limit running instance to one
if cap(cui.Integrations[i].run) != 1 {
cui.Integrations[i].run = make(chan struct{}, 1)
}
select {
case cui.Integrations[i].run <- struct{}{}:
defer func() { <-cui.Integrations[i].run }()
default:
panic(fmt.Errorf("another task of %s is running", name))
}
result, err := v.Cmd()
if err != nil {
panic(err)