supervisor

package
v0.0.0-...-efbc44a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: GPL-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package supervisor runs the child processes and keeps them running.

keepalived is three processes: a parent that supervises, and children for VRRP and for the health checkers (plus BFD when configured). The split is not cosmetic — it is what lets a crash in the health-check code, which parses responses from arbitrary backends, avoid taking VRRP down with it.

The parent's whole job is: start the children, restart them when they die, forward reload and dump signals, and shut them down in a bounded time. This package is that logic with the process mechanics behind an interface, so the restart back-off and the shutdown deadline can be tested without spawning anything.

Index

Constants

View Source
const (
	// BackoffReset is how long a child must run before its back-off is
	// forgotten. C tests `> 60` seconds, or exactly 60 with the
	// microseconds no earlier (scheduler.c:496-498).
	BackoffReset = 60 * time.Second
	// BackoffMax is the ceiling.
	BackoffMax = 60 * time.Second
)

Back-off for restarting a child that died (calc_restart_delay, lib/scheduler.c:490-527).

The sequence is 0, 1, 2, 4, 8, 16, 32, 60, 60, … seconds. The first restart is immediate, which is the right default: a child that died once is far more likely to have hit something transient than to be in a crash loop, and delaying VRRP's restart by even a second risks a failover.

The doubling exists for the other case. A child that crashes on startup would otherwise be respawned as fast as fork returns, and the log would fill faster than anyone could read why.

View Source
const (
	// ExitOK is a clean exit.
	ExitOK = 0
	// ExitFatal is a general failure (EXIT_FAILURE).
	ExitFatal = 1
	// ExitConfigError is an unusable configuration (EXIT_INVALIDARGUMENT).
	// A child returning this tells the parent not to restart it: the same
	// configuration would fail the same way.
	ExitConfigError = 2
	// ExitMissingPermission is EXIT_NOPERMISSION — the daemon lacks a
	// capability it needs, typically CAP_NET_RAW or CAP_NET_ADMIN.
	ExitMissingPermission = 4
	// ExitNoConfig is EXIT_NOTCONFIGURED: the file parsed but asked for
	// nothing to be done.
	ExitNoConfig = 6
	// ExitProgramError is EX_SOFTWARE, an internal inconsistency.
	ExitProgramError = 70
	// ExitNoMemory is EXIT_MEMORY.
	ExitNoMemory = 204
)

Process exit codes (enum exit_code and enum chk_exit_code, lib/scheduler.h:205-220).

These are a contract, not an implementation detail. systemd reads them — they are the values systemd.exec(5) documents — and every deployment script that runs `keepalived -t` branches on them. A port that returned 1 for everything would leave those scripts unable to tell a syntax error from a permission problem, which is precisely the distinction they exist to make.

View Source
const (
	// TestExitOK means the configuration is usable.
	TestExitOK = 0
	// TestExitConfig means the file was missing, named an unusable
	// interface, or hit a fatal error.
	TestExitConfig = 4
	// TestExitConfigTest means the configuration has errors that are not
	// fatal — the daemon would start, with less than was asked for.
	TestExitConfigTest = 5
	// TestExitConfigSecurity means a security check failed: a script or key
	// file with permissions that make it untrustworthy. It is separate
	// because it is the one an automated deployment must never ignore.
	TestExitConfigSecurity = 6
)

Configuration-test exit codes (enum chk_exit_code, lib/scheduler.h:215-219).

They are a *separate* numbering from the run-time codes and deliberately do not overlap, so a script can tell "the configuration is bad" from "the daemon failed to start". C's comment on the first value — "Maintain backward compatibility" — is why 4 rather than something adjacent to the others.

View Source
const ChildWait = 5 * time.Second

ChildWait is how long the parent waits for children to exit before killing them (CHILD_WAIT_SECS, keepalived/core/main.c:124).

Variables

View Source
var ErrChildFailed = errors.New("supervisor: child process failed")

ErrChildFailed is returned by Run when a child exited fatally, or when respawning is disabled and one died.

Functions

This section is empty.

Types

type Backoff

type Backoff struct {
	// contains filtered or unexported fields
}

Backoff tracks one child's restart delay.

The value returned is the one *before* the update, which is why the first restart is immediate: C captures `restart_delay = *next_restart_delay` and then advances the state (scheduler.c:492, :526). Reading it the other way round gives a one-second delay on the first crash and shifts the whole sequence, which is the sort of off-by-one that only shows up as a slightly slower failover under load.

func (*Backoff) Next

func (b *Backoff) Next(startedAt, now time.Time) time.Duration

Next returns how long to wait before restarting a child that started at startedAt and died at now, and advances the back-off.

func (*Backoff) Pending

func (b *Backoff) Pending() time.Duration

Pending reports the delay the next crash would incur, for logging.

func (*Backoff) Reset

func (b *Backoff) Reset()

Reset forgets the back-off, for a child being restarted deliberately rather than after a crash.

type Child

type Child interface {
	// Name identifies the child in logs.
	Name() string
	// Start launches it. The returned channel receives exactly once, when
	// the child ends.
	Start(ctx context.Context) (<-chan Exit, error)
	// Signal forwards a signal to a running child.
	Signal(sig Signal) error
	// Kill terminates it immediately, for the case where the terminate
	// deadline passed.
	Kill() error
}

Child is one supervised process.

It is an interface so the supervisor's policy — back-off, shutdown ordering, the terminate deadline — is testable without forking. ExecChild is the production implementation.

type Clock

type Clock interface {
	Now() time.Time
	After(d time.Duration) <-chan time.Time
}

Clock is the supervisor's view of time, so the back-off and the shutdown deadline can be tested without waiting for them.

type Config

type Config struct {
	Children []Child
	Clock    Clock
	Logger   Logger
	// NoRespawn is --dont-respawn: a child that dies takes the parent down
	// with it. It exists for debugging, where an automatic restart hides
	// the crash being investigated.
	NoRespawn bool
	// ChildWait overrides the shutdown deadline.
	ChildWait time.Duration
}

Config is the supervisor's configuration.

type ExecChild

type ExecChild struct {
	// ChildName identifies it in logs and in the supervisor's map.
	ChildName string
	// Path and Args are the program to run.
	Path string
	Args []string
	// Env is the child's environment. Nil inherits the parent's.
	Env []string
	// contains filtered or unexported fields
}

ExecChild is a Child backed by a real process.

It is the production implementation; everything the supervisor's policy does is tested against fakes instead, because a back-off sequence and a shutdown deadline are not things to verify by forking.

func (*ExecChild) Kill

func (c *ExecChild) Kill() error

Kill terminates the child's process group immediately.

func (*ExecChild) Name

func (c *ExecChild) Name() string

Name implements Child.

func (*ExecChild) Signal

func (c *ExecChild) Signal(sig Signal) error

Signal forwards a signal to the child's process group.

func (*ExecChild) Start

func (c *ExecChild) Start(ctx context.Context) (<-chan Exit, error)

Start launches the process.

The child gets its own process group so that a signal aimed at it reaches anything it spawns — a VRRP child runs notify scripts, and those must not outlive it. It does *not* get Pdeathsig: the parent's whole job is to supervise, and a child that died because the parent restarted would defeat that. The parent kills them explicitly instead, with a deadline.

type Exit

type Exit struct {
	// Name identifies which child.
	Name string
	// Code is the exit status, valid unless Signal is non-zero.
	Code int
	// Signal is the signal that killed it, or zero.
	Signal int
	// Err is set when the child could not be waited for at all.
	Err error
}

Exit describes how a child ended.

func (Exit) Fatal

func (e Exit) Fatal() bool

Fatal reports whether this exit means the whole daemon should stop rather than the child be restarted.

C's rule (report_child_status): a child that exits with a status the parent recognises as a configuration failure takes the parent down with it, because restarting it would only reach the same configuration again. A crash — a signal, or an unrecognised status — is respawned.

func (Exit) String

func (e Exit) String() string

type Logger

type Logger interface {
	Infof(format string, args ...any)
	Alertf(format string, args ...any)
}

Logger receives the supervisor's messages.

type Signal

type Signal int

Signal is the subset of signals the parent forwards.

const (
	// SigTerminate asks a child to shut down cleanly. For VRRP that means
	// sending the priority-0 resignation advert, which is the difference
	// between a 609 ms and a 2923 ms failover.
	SigTerminate Signal = iota
	// SigReload asks a child to re-read its configuration.
	SigReload
	// SigDumpData and SigDumpStats ask for a diagnostic dump.
	SigDumpData
	// SigDumpStats asks for a statistics dump.
	SigDumpStats
)

func (Signal) String

func (s Signal) String() string

type Supervisor

type Supervisor struct {
	// contains filtered or unexported fields
}

Supervisor runs the children.

func New

func New(cfg Config) (*Supervisor, error)

New creates a supervisor.

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context) error

Run starts the children and supervises them until ctx is cancelled or a child fails fatally.

On return every child has been asked to terminate and either exited or been killed, so a caller that returns from Run can exit the process without leaving a VRRP instance holding addresses.

func (*Supervisor) Signal

func (s *Supervisor) Signal(sig Signal)

Signal forwards a signal to every running child.

Reload is the one that matters: C's parent re-reads nothing itself, it just tells the children to (process_reload_signal, main.c:945). A child that cannot reload keeps running with its old configuration rather than dying, which is the right failure mode — a bad configuration file must not take down a working router.

type SystemClock

type SystemClock struct{}

SystemClock is the real clock.

func (SystemClock) After

func (SystemClock) After(d time.Duration) <-chan time.Time

func (SystemClock) Now

func (SystemClock) Now() time.Time

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL