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 { type Action struct {
Name string `json:"name"` Name string `json:"name"`
Cmd string `json:"cmd"` Cmd string `json:"cmd"`
run chan struct{} `json:"-"`
pid int `json:"-"`
} }
type Integration struct { type Integration struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Cmd func() (string, error) `json:"-"` Cmd func() (string, error) `json:"-"`
run chan struct{} `json:"-"`
} }
type ConfigUI struct { type ConfigUI struct {

32
file.go
View File

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

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time"
"kumoly.io/lib/ksrv" "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) { func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name") name := r.URL.Query().Get("name")
for _, v := range cui.Actions { for i, v := range cui.Actions {
if v.Name == name { 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} 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) result, err := file.Do(cui.cmdTimeout)
if err != nil { if err != nil {
panic(err) 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) { func (cui *ConfigUI) DoIntegration(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name") name := r.URL.Query().Get("name")
for _, v := range cui.Integrations { for i, v := range cui.Integrations {
if v.Name == name { 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() result, err := v.Cmd()
if err != nil { if err != nil {
panic(err) panic(err)