mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-03 13:01:16 +02:00
This adds generic goroutine runner which simply wraps service as process goroutine. It supports log redirection and basic panic handling. DHCP-related part of the network package was slightly adjusted to run as service with logging updates (to redirect logs to a file) and context canceling. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
155 lines
3.4 KiB
Go
155 lines
3.4 KiB
Go
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package process
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/talos-systems/talos/internal/app/init/pkg/system/events"
|
|
processlogger "github.com/talos-systems/talos/internal/app/init/pkg/system/log"
|
|
"github.com/talos-systems/talos/internal/app/init/pkg/system/runner"
|
|
"github.com/talos-systems/talos/internal/pkg/constants"
|
|
"github.com/talos-systems/talos/pkg/userdata"
|
|
)
|
|
|
|
// processRunner is a runner.Runner that runs a process on the host.
|
|
type processRunner struct {
|
|
data *userdata.UserData
|
|
args *runner.Args
|
|
opts *runner.Options
|
|
|
|
stop chan struct{}
|
|
stopped chan struct{}
|
|
}
|
|
|
|
// NewRunner creates runner.Runner that runs a process on the host
|
|
func NewRunner(data *userdata.UserData, args *runner.Args, setters ...runner.Option) runner.Runner {
|
|
r := &processRunner{
|
|
data: data,
|
|
args: args,
|
|
opts: runner.DefaultOptions(),
|
|
stop: make(chan struct{}),
|
|
stopped: make(chan struct{}),
|
|
}
|
|
|
|
for _, setter := range setters {
|
|
setter(r.opts)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// Open implements the Runner interface.
|
|
func (p *processRunner) Open(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// Run implements the Runner interface.
|
|
func (p *processRunner) Run(eventSink events.Recorder) error {
|
|
defer close(p.stopped)
|
|
|
|
return p.run(eventSink)
|
|
}
|
|
|
|
// Stop implements the Runner interface
|
|
func (p *processRunner) Stop() error {
|
|
close(p.stop)
|
|
|
|
<-p.stopped
|
|
|
|
p.stop = make(chan struct{})
|
|
p.stopped = make(chan struct{})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close implements the Runner interface.
|
|
func (p *processRunner) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *processRunner) build() (cmd *exec.Cmd, err error) {
|
|
cmd = exec.Command(p.args.ProcessArgs[0], p.args.ProcessArgs[1:]...)
|
|
|
|
// Set the environment for the service.
|
|
cmd.Env = append([]string{fmt.Sprintf("PATH=%s", constants.PATH)}, p.opts.Env...)
|
|
|
|
// Setup logging.
|
|
w, err := processlogger.New(p.args.ID, p.opts.LogPath)
|
|
if err != nil {
|
|
err = fmt.Errorf("service log handler: %v", err)
|
|
return
|
|
}
|
|
|
|
var writer io.Writer
|
|
if p.data.Debug {
|
|
writer = io.MultiWriter(w, os.Stdout)
|
|
} else {
|
|
writer = w
|
|
}
|
|
cmd.Stdout = writer
|
|
cmd.Stderr = writer
|
|
|
|
return cmd, nil
|
|
}
|
|
|
|
func (p *processRunner) run(eventSink events.Recorder) error {
|
|
cmd, err := p.build()
|
|
if err != nil {
|
|
return errors.Wrap(err, "error building command")
|
|
}
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
return errors.Wrap(err, "error starting process")
|
|
}
|
|
|
|
eventSink(events.StateRunning, "Process %s started with PID %d", p, cmd.Process.Pid)
|
|
|
|
waitCh := make(chan error)
|
|
|
|
go func() {
|
|
waitCh <- cmd.Wait()
|
|
}()
|
|
|
|
select {
|
|
case err = <-waitCh:
|
|
// process exited
|
|
return err
|
|
case <-p.stop:
|
|
// graceful stop the service
|
|
eventSink(events.StateStopping, "Sending SIGTERM to %s", p)
|
|
|
|
// nolint: errcheck
|
|
_ = cmd.Process.Signal(syscall.SIGTERM)
|
|
}
|
|
|
|
select {
|
|
case <-waitCh:
|
|
// stopped process exited
|
|
return nil
|
|
case <-time.After(p.opts.GracefulShutdownTimeout):
|
|
// kill the process
|
|
eventSink(events.StateStopping, "Sending SIGKILL to %s", p)
|
|
|
|
// nolint: errcheck
|
|
_ = cmd.Process.Signal(syscall.SIGKILL)
|
|
}
|
|
|
|
// wait for process to terminate
|
|
<-waitCh
|
|
return nil
|
|
}
|
|
|
|
func (p *processRunner) String() string {
|
|
return fmt.Sprintf("Process(%q)", p.args.ProcessArgs)
|
|
}
|