systemd MemoryPressureWatch: Handle Per-Service PSI Events Correctly
Connect systemd's per-service PSI pressure protocol to application cache policy, while keeping it distinct from cgroup limits and OOM killing.
Linux Pressure Stall Information (PSI) can tell a program when memory contention is already costing tasks time. systemd adds a service-manager contract that makes a unit’s pressure source and threshold available to the service process. This is useful when an application owns reclaimable caches and can reduce future memory use before the kernel is forced into more disruptive recovery. It is not a memory limit, a leak detector, or an instruction for systemd-oomd to kill the service. Keep those controls separate from the application’s response. systemd: Resource Pressure Handling Linux kernel PSI documentation
Start with the unit’s pressure contract
MemoryPressureWatch= enables or disables the protocol for a service; systemd documents it as available since systemd 254. With on, systemd enables the unit’s memory accounting, makes its cgroup memory.pressure file accessible to the service user, and passes the path in MEMORY_PRESSURE_WATCH. The configured threshold is conveyed separately through MEMORY_PRESSURE_WRITE. MemoryPressureThresholdSec= describes a maximum allocation-latency threshold per two-second window; absent an override, the system-wide default is 200 milliseconds. These are manager settings, not application-owned cgroup files to guess from a process ID. systemd.resource-control(5)
For a systemd-managed service, configuration can be explicit:
[Service]
MemoryPressureWatch=on
MemoryPressureThresholdSec=150ms
Reload the manager after editing a unit and verify the effective configuration. A unit can inherit defaults, and an older manager may not recognize the directive; successful deployment of the file does not prove the process received the environment contract.
Interpret the watch path by inode type
MEMORY_PRESSURE_WATCH is an absolute path. The protocol can identify a PSI regular file and recommends that implementations also support a FIFO or filesystem Unix-domain stream socket. MEMORY_PRESSURE_WRITE, when present, is Base64-encoded bytes; decode and write it immediately after opening or connecting. Do not parse it as a stable text format: arbitrary binary data, including NUL, is permitted.
The readiness event depends on the inode type. For a PSI regular file, monitor POLLPRI and do not read from the descriptor. For a FIFO or Unix stream socket, monitor POLLIN and read and discard queued notification bytes. If the watch variable is the literal /dev/null, disable the feature. A generic helper that always expects POLLIN, or that reads the PSI file because it is readable, does not implement the contract. systemd pressure protocol
Choose an application action before enabling notifications
A notification is a hint that the watched resource has crossed its configured pressure threshold. It is not a continuous stream of increasing severity levels. systemd describes events as occurring when pressure is detected, rather than continuously while the condition remains present; the protocol currently communicates one configured degree per resource. That makes an idempotent response practical: reduce a cache’s future admission budget, release a bounded pool of already-discardable objects, or defer speculative work. Avoid doing a full heap walk or synchronous disk/network operation in a latency-sensitive event callback.
Services using sd-event can delegate protocol handling to sd_event_add_memory_pressure(). Its NULL handler selects systemd’s default sd_event_trim_memory() behavior, which trims libsystemd allocation caches and calls glibc malloc_trim(). A service with its own cache can provide a callback instead:
static int on_pressure(sd_event_source *source, void *userdata) {
struct cache_policy *policy = userdata;
(void) source;
reduce_future_cache_admission(policy);
release_idle_cache_entries(policy, 32);
return 0;
}
sd_event_source *pressure_source = NULL;
int r = sd_event_add_memory_pressure(event, &pressure_source,
on_pressure, &policy);
if (r < 0)
/* report or deliberately disable optional pressure handling */;
The callback sketch is application policy, not a systemd-provided cache API. The handler must respect the service’s own thread-safety and latency constraints. On teardown, release the event source using the library’s normal ownership rules before destroying callback state.
Test the manager-to-process path
Test three layers: inspect the unit’s effective watch and threshold, log whether the variables are present without dumping decoded bytes, and exercise the handler through a deterministic test seam. On a controlled Linux host, verify the cgroup has PSI files and the process can open the supplied path. Per-cgroup PSI depends on kernel/cgroup support; systemd’s protocol also depends on manager support and unit placement. Containers, delegated subtrees, and other service managers need compatibility checks.
Do not manufacture memory exhaustion on a shared machine. Inject a synthetic event into the policy layer, then run bounded integration tests on an expendable VM. Record systemd version, cgroup mode, effective unit properties, threshold, event count, and bytes released. Keep PSI alerts, hard cgroup limits, and OOM policy separate: they answer different questions and fail differently.
Related:
- Linux Pressure Stall Information: Measuring CPU, Memory, and I/O Contention Directly
- Linux cgroup v2 memory.reclaim: Controlled Proactive Reclaim
Sources: