feat: suspend jobs via ctrl z

this was a bit of a pain to add and it still doesnt work
properly on this commit, but this is how it would be done
job-suspend
sammyette 2023-02-19 15:13:52 -04:00
parent 26c8f28034
commit f239363a60
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
6 changed files with 107 additions and 18 deletions

22
api.go
View File

@ -47,6 +47,7 @@ var exports = map[string]util.LuaExport{
"interval": {hlinterval, 2, false},
"read": {hlread, 1, false},
"run": {hlrun, 1, true},
"suspend": {hlsuspend, 0, false},
"timeout": {hltimeout, 2, false},
"which": {hlwhich, 1, false},
}
@ -88,6 +89,12 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
if err != nil {
return nil, errors.New("hilbish.hinter has to be a function")
}
} else if k == "suspend" {
var err error
_, err = c.ClosureArg(2)
if err != nil {
return nil, errors.New("hilbish.suspend has to be a function")
}
} else if modVal := mod.Get(rt.StringValue(k)); modVal != rt.NilValue {
return nil, errors.New("not allowed to override in hilbish table")
}
@ -213,7 +220,7 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
var exitcode uint8
stdout, stderr, err := execCommand(cmd, terminalOut)
stdout, stderr, err := execCommand(cmd, terminalOut, false)
if code, ok := interp.IsExitStatus(err); ok {
exitcode = code
@ -643,3 +650,16 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
// suspend()
// Suspends the currently running process. This can (and should be) overwritten if a
// a different runner is being used.
// --- @param line string
func hlsuspend(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if currentHandle != nil || currentHandle.Process != nil {
j := jobs.addFromHandler(currentCmd, currentHandle)
j.suspend()
}
return c.Next(), nil
}

52
exec.go
View File

@ -27,6 +27,8 @@ import (
var errNotExec = errors.New("not executable")
var errNotFound = errors.New("not found")
var runnerMode rt.Value = rt.StringValue("hybrid")
var currentHandle *exec.Cmd
var currentCmd string
type execError struct{
typ string
@ -233,7 +235,7 @@ func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr
}
func execSh(cmdString string) (string, uint8, bool, error) {
_, _, err := execCommand(cmdString, true)
_, _, err := execCommand(cmdString, true, true)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
@ -254,7 +256,7 @@ func execSh(cmdString string) (string, uint8, bool, error) {
}
// Run command in sh interpreter
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
func execCommand(cmd string, terminalOut, interactiveCall bool) (io.Writer, io.Writer, error) {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil {
return nil, nil, err
@ -277,16 +279,21 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
var bg bool
for _, stmt := range file.Stmts {
bg = false
printer.Print(buf, stmt.Cmd)
stmtStr := buf.String()
buf.Reset()
if interactiveCall {
currentCmd = stmtStr
}
if stmt.Background {
bg = true
printer.Print(buf, stmt.Cmd)
stmtStr := buf.String()
buf.Reset()
jobs.add(stmtStr, []string{}, "")
}
interp.ExecHandler(execHandle(bg))(runner)
interp.ExecHandler(execHandle(bg, interactiveCall))(runner)
err = runner.Run(context.TODO(), stmt)
if err != nil {
return stdout, stderr, err
@ -296,7 +303,7 @@ func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
return stdout, stderr, nil
}
func execHandle(bg bool) interp.ExecHandlerFunc {
func execHandle(bg, interactiveCall bool) interp.ExecHandlerFunc {
return func(ctx context.Context, args []string) error {
_, argstring := splitInput(strings.Join(args, " "))
// i dont really like this but it works
@ -410,6 +417,10 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
Stderr: hc.Stderr,
}
if interactiveCall {
currentHandle = &cmd
}
var j *job
if bg {
j = jobs.getLatest()
@ -419,6 +430,8 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
err = cmd.Start()
}
execDone := make(chan struct{}, 1)
var suspended bool
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
@ -440,12 +453,31 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
}()
}
err = cmd.Wait()
go func() {
hooks.On("job.suspend", func(args ...interface{}) {
fmt.Println("suspended")
val := args[0].(rt.Value)
_, ok := valueToJob(val)
if !ok {
return
}
suspended = true
execDone <- struct{}{}
})
}()
go func() {
err = cmd.Wait()
execDone <- struct{}{}
}()
<-execDone
}
exit := handleExecErr(err)
if bg {
if bg && !suspended {
j.exitCode = int(exit)
j.finish()
}

12
job.go
View File

@ -268,6 +268,18 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
return jb
}
func (j *jobHandler) addFromHandler(cmd string, handler *exec.Cmd) *job {
jb := j.add(cmd, handler.Args, handler.Path)
if ps := handler.ProcessState; ps != nil {
if !ps.Exited() && ps.Pid() != 0 {
jb.running = true
}
}
jb.setHandle(handler)
return jb
}
func (j *jobHandler) getLatest() *job {
j.mu.RLock()
defer j.mu.RUnlock()

View File

@ -8,6 +8,7 @@ import (
"syscall"
"golang.org/x/sys/unix"
rt "github.com/arnodel/golua/runtime"
)
func (j *job) foreground() error {
@ -36,3 +37,14 @@ func (j *job) background() error {
proc.Signal(syscall.SIGCONT)
return nil
}
func (j *job) suspend() error {
proc := j.handle.Process
if proc == nil {
return nil
}
proc.Signal(syscall.SIGSTOP)
hooks.Emit("job.suspend", rt.UserDataValue(j.ud))
return nil
}

View File

@ -13,3 +13,7 @@ func (j *job) foreground() error {
func (j *job) background() error {
return errors.New("not supported on windows")
}
func (j *job) suspend() error {
return errors.New("not supported on windows")
}

View File

@ -3,23 +3,32 @@
package main
import (
"fmt"
"syscall"
"os"
"os/signal"
rt "github.com/arnodel/golua/runtime"
)
func handleSignals() {
c := make(chan os.Signal)
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN, syscall.SIGTSTP)
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT)
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGQUIT, syscall.SIGTSTP)
for s := range c {
switch s {
case os.Interrupt: hooks.Emit("signal.sigint")
case syscall.SIGTERM: exit(0)
case syscall.SIGWINCH: hooks.Emit("signal.resize")
case syscall.SIGUSR1: hooks.Emit("signal.sigusr1")
case syscall.SIGUSR2: hooks.Emit("signal.sigusr2")
case os.Interrupt: hooks.Emit("signal.sigint")
case syscall.SIGTERM: exit(0)
case syscall.SIGWINCH: hooks.Emit("signal.resize")
case syscall.SIGUSR1: hooks.Emit("signal.sigusr1")
case syscall.SIGUSR2: hooks.Emit("signal.sigusr2")
case syscall.SIGTSTP:
suspendHandler := hshMod.Get(rt.StringValue("suspend"))
_, err := rt.Call1(l.MainThread(), suspendHandler)
if err != nil {
fmt.Println(err)
}
}
}
}