proctune

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: 4 Imported by: 0

Documentation

Overview

Package proctune applies the process-level settings global_defs asks for: the nice value, memory locking and the real-time CPU limit.

They are grouped because they are the same kind of thing — properties of the process rather than of any instance — and because keepalived couples them. The scheduling policy itself lives in internal/rtsched, which is the one of these that has to be applied per thread.

Why any of this matters to a router

A VRRP backup declares its master down after roughly three advertisement intervals. Everything here is about being *runnable* when that deadline arrives. A daemon whose pages have been swapped out takes a major fault to send an advertisement, and a major fault is milliseconds; one at the back of a busy run queue waits its turn. Neither shows up in a test on an idle machine, and both decide whether a healthy router is declared dead.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(s Settings, logf func(string, ...any))

Apply applies what the settings ask for, reporting every failure rather than stopping at the first.

None of them is fatal. A daemon that could not lock its memory is a daemon that may be slower than asked for under memory pressure, which is worth saying and is not worth refusing to route over.

func ApplyBFD

func ApplyBFD(s BFDSettings, logf func(string, ...any))

ApplyBFD applies the settings to the calling thread.

The caller must have locked the goroutine to its thread first, or the settings land on whichever thread happened to be running and the goroutine migrates away from them at the next scheduling point — which is worse than not applying them, because it looks applied.

func CPUAffinity

func CPUAffinity(cpu int) error

CPUAffinity pins the process to one CPU.

keepalived takes a single CPU number rather than a mask. Pinning is about cache residency and about not being migrated at the moment a deadline arrives — a thread moved between cores arrives with a cold cache and, worse, after a scheduling decision it did not need to wait for.

func LockMemory

func LockMemory() error

LockMemory keeps the process resident (set_process_dont_swap, lib/process.c:69-98).

MCL_CURRENT and MCL_FUTURE together, so pages mapped later are locked too — a Go heap grows after startup, and locking only what exists at the moment of the call would leave almost all of it swappable.

MCL_ONFAULT is deliberately not set. C adds it where the kernel has it, which locks pages as they are faulted rather than up front; that is cheaper for a process with a large sparse mapping and it is the wrong trade here, because the whole point is to have taken the faults *before* the deadline arrives rather than during it.

The stack reserve C pre-faults has no equivalent: goroutine stacks are allocated from the heap and grow by copying, so there is no single stack to touch. MCL_FUTURE covers the growth instead.

func Nice

func Nice(priority int) error

Nice sets the process's scheduling nice value (set_process_priority, lib/process.c:109-124).

The range is C's, [-20, 19], and the parser has already reported anything outside it. A negative value needs CAP_SYS_NICE.

It is *not* applied when a real-time priority is in use, and that is C's rule rather than an optimisation: SCHED_RR ignores the nice value entirely, so setting both would leave a configuration that reads as though the nice value did something.

func RLimitRTTime

func RLimitRTTime(limit time.Duration) error

RLimitRTTime bounds how long the process may run without yielding while it holds a real-time policy (set_process_priorities, lib/process.c:160-172).

It is the safety catch on SCHED_RR. A real-time thread that spins does not get preempted by anything at a lower priority, which on a single-CPU machine means nothing else runs at all — including whatever an operator would use to kill it. RLIMIT_RTTIME makes the kernel intervene: SIGXCPU at the soft limit, SIGKILL at the hard one.

The soft limit is half the configured value, which is C's, and the reason is the signal: the soft limit exists to be *survivable*, so that a process approaching the limit is told before it is killed at it.

Applied only alongside a real-time priority, again as C does — the limit only counts real-time execution, so on a normal policy it can never fire and setting it would suggest a protection that is not there.

func SetProcessName

func SetProcessName(name string) error

SetProcessName renames the running process (process_names, and the four per-daemon names).

It sets the kernel's comm field, which is what ps reports and what /proc/PID/comm holds. That is a different thing from what C does — C rewrites argv, so `ps auxww` shows the new name too — and the difference matters enough to state: comm is limited to fifteen characters and the name is truncated to fit, where argv is not.

Rewriting argv from Go is not available: the runtime keeps its own copy of the original pointers and there is no supported way to extend the region. Setting comm is the part that can be done correctly, and an operator running `ps -o comm` or reading /proc sees the configured name.

The name matters operationally rather than cosmetically. A machine running two keepalived instances in different namespaces shows four identical processes otherwise, and neither `pkill` nor a monitoring check can tell which is which.

func SetUmask

func SetUmask(mask int)

SetUmask sets the file-creation mask.

It applies to everything the daemon creates afterwards: pid files, the notify FIFOs, a dump file. The default is inherited from whatever started the daemon, which for a unit file is not obviously anything.

func ThreadAffinity

func ThreadAffinity(cpu int) error

ThreadAffinity pins the calling thread to one CPU.

func ThreadNice

func ThreadNice(priority int) error

ThreadNice sets the calling thread's nice value.

PRIO_PROCESS with a thread id is per-thread on Linux, despite the name: the kernel's "process" here is a task, and a task is a thread. That is what makes bfd_priority expressible at all in a daemon whose BFD is a goroutine.

func UnlockMemory

func UnlockMemory() error

UnlockMemory releases the lock.

Types

type BFDSettings

type BFDSettings struct {
	// Nice is bfd_priority, applied to this thread. Ignored when RTPriority
	// is set, as for the process-wide equivalent.
	Nice int
	// RTPriority is bfd_rt_priority: the SCHED_RR priority for this thread.
	RTPriority int
	// RLimitRTTime is bfd_rlimit_rttime. Process-wide, because
	// RLIMIT_RTTIME is.
	RLimitRTTime time.Duration
	// CPUAffinity is bfd_cpu_affinity, pinning this thread. Negative leaves
	// it alone.
	CPUAffinity int
	// NoSwap is bfd_no_swap. Process-wide, because mlockall is.
	NoSwap bool
}

BFDSettings is what the bfd_* keywords asked for.

func (BFDSettings) Set

func (s BFDSettings) Set() bool

Set reports whether anything was asked for.

type Settings

type Settings struct {
	// Nice is the scheduling nice value, ignored when RTPriority is set.
	Nice int
	// NoSwap locks the process into memory.
	NoSwap bool
	// RTPriority is the SCHED_RR priority, applied elsewhere; it appears
	// here only because it decides which of the other two are meaningful.
	RTPriority int
	// RLimitRTTime bounds real-time execution, ignored without RTPriority.
	RLimitRTTime time.Duration
	// CPUAffinity pins the process to one CPU. Negative leaves it alone;
	// keepalived's keyword takes a single number, not a mask.
	CPUAffinity int
	// Umask is the file-creation mask for everything the daemon makes
	// afterwards. Zero leaves the inherited one.
	Umask int
	// ProcessName renames the process. Empty leaves it alone.
	ProcessName string
}

Settings is what a daemon was asked to apply to itself.

Jump to

Keyboard shortcuts

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