wip: daemon

This commit is contained in:
2023-09-01 12:29:31 +10:00
parent eac78ec322
commit 91147f073f
8 changed files with 332 additions and 30 deletions

View File

@@ -1,9 +1,71 @@
package daemon
import "github.com/spf13/cobra"
import (
"gitea.suyono.dev/suyono/wingmate/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os/exec"
)
type InstanceType int
type WingMateDescriptor interface {
String() string
Name() string
InstanceName() string
InstanceType() InstanceType
Error() string // so the descriptor can be wrapped inside an error
}
type process struct {
cmd *exec.Cmd
descriptor WingMateDescriptor
}
type daemon struct {
running map[string]process
available map[string]process
services map[string]WingMateDescriptor
cron map[string]WingMateDescriptor
}
const (
Service InstanceType = iota
Cron
)
func (t InstanceType) String() string {
switch t {
case Service:
return config.ServiceKey
case Cron:
return config.CronKey
default:
return "[unknown instance type]"
}
}
func Start(cmd *cobra.Command, args []string) error {
var (
err error
)
_, _ = cmd, args // prevent warnings for unused arguments
d := &daemon{
running: make(map[string]process),
available: make(map[string]process),
services: make(map[string]WingMateDescriptor),
cron: make(map[string]WingMateDescriptor),
}
if err = d.buildServiceMap(); err != nil {
return err
}
return nil
}
func (d *daemon) buildServiceMap() error {
for s := range viper.GetStringMap(Service.String()) {
_ = s //TODO: resume work
}
return nil
}

View File

@@ -1,23 +1,117 @@
package daemon
import (
"bufio"
"fmt"
"gitea.suyono.dev/suyono/wingmate/debugframes"
"io"
"os"
"os/exec"
)
type ProcessConfig interface {
Descriptor() WingMateDescriptor
// Name returns the binary name to be executed, to be passed in to exec.Command
Name() string
// Args returns the arguments for the process, to be passed in to exec.Command
Args() []string
Env() map[string]string
Dir() string
WorkingDir() string
LogChannel() chan<- any
ControlChannel() chan<- any
}
func StartProcess(config ProcessConfig) error {
cmd := exec.Command(config.Name(), config.Args()...)
for k, v := range config.Env() {
type StreamID int
type ProcessLogEntry struct {
ID WingMateDescriptor
LogEntry string
}
const (
Stdout StreamID = iota
Stderr
)
func (s StreamID) String() string {
switch s {
case Stdout:
return "stdout"
case Stderr:
return "stderr"
default:
return "[unknown stream]"
}
}
func StartProcess(config ProcessConfig) (*exec.Cmd, error) {
var (
err error
cmd *exec.Cmd
k, v string
stdoutPipe, stderrPipe io.ReadCloser
)
cmd = exec.Command(config.Name(), config.Args()...)
cmd.Env = os.Environ()
for k, v = range config.Env() {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
}
cmd.Dir = config.Dir()
cmd.Dir = config.WorkingDir()
if stdoutPipe, err = cmd.StdoutPipe(); err != nil {
return nil, fmt.Errorf("set up stdout pipe on process %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
}
if stderrPipe, err = cmd.StderrPipe(); err != nil {
return nil, fmt.Errorf("set up stderr pipe on process %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
}
return nil
if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("starting process %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
}
go serviceLogReader(config, stdoutPipe, Stdout)
go serviceLogReader(config, stderrPipe, Stderr)
go serviceWaiter(config, cmd)
return cmd, nil
}
func serviceLogReader(config ProcessConfig, stream io.Reader, id StreamID) {
out := config.LogChannel()
defer func() {
if o := recover(); o != nil {
out <- o
//TODO: include stack trace, make the return object an error
}
}()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
out <- ProcessLogEntry{
ID: config.Descriptor(),
LogEntry: scanner.Text(),
}
}
if err := scanner.Err(); err != nil {
out <- fmt.Errorf("read stream from %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
}
}
func serviceWaiter(config ProcessConfig, cmd *exec.Cmd) {
ctrl := config.ControlChannel()
defer func() {
if o := recover(); o != nil {
ctrl <- o
//TODO: include stack trace
}
}()
if err := cmd.Wait(); err != nil {
ctrl <- fmt.Errorf("wait error on service %w: %w", config.Descriptor(), err)
} else {
ctrl <- config.Descriptor()
}
}

View File

@@ -1,4 +1,12 @@
package daemon
func DetectDeadChildProcess() {
import "golang.org/x/sys/unix"
const (
anyChildProcess = -1
)
func WaitChildProcess() (int, error) {
var ws unix.WaitStatus
return unix.Wait4(anyChildProcess, &ws, 0, nil)
}

View File

@@ -15,7 +15,6 @@ func TestWait(t *testing.T) {
err error
pid int
testpid int
ws unix.WaitStatus
)
cmd := exec.Command("sleep", "10")
@@ -37,22 +36,14 @@ func TestWait(t *testing.T) {
}
}()
testpid, err = unix.Wait4(-1, &ws, 0, nil)
testpid, err = WaitChildProcess()
if err != nil {
t.Fatal("wait4 error:", err)
}
if ws.Signaled() {
t.Log("the child process got signal:", ws.Signal())
}
if ws.Exited() {
t.Log("the child process exit with status:", ws.ExitStatus())
}
// if err = cmd.Wait(); err != nil {
// t.Fatal("is this expected?", err)
// }
//if err = cmd.Wait(); err != nil {
// t.Fatal("is this expected?", err)
//}
assert.Equal(t, pid, testpid)
}