From 693cd75bf748a20bd7421389afe470b0fc14375a Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Mon, 6 Apr 2026 00:33:26 -0400 Subject: [PATCH] klippy: lock process memory to RAM at startup via mlockall Call mlockall(MCL_CURRENT | MCL_FUTURE) immediately after logging is initialised in main(). This pins all current and future memory pages into physical RAM so the OS cannot swap them out. For a real-time-sensitive host process like Klippy, page faults caused by the kernel reclaiming and later faulting-in swapped pages can introduce multi-millisecond latency spikes. These manifest as missed steps or timing errors that are otherwise hard to diagnose. MCL_CURRENT locks pages already mapped at the time of the call. MCL_FUTURE locks any pages mapped after the call (e.g. from dlopen, mmap, stack growth). The call requires CAP_IPC_LOCK (or root). If the capability is absent the failure is logged as a warning rather than a hard error so that development environments and containers that do not grant the capability continue to work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- klippy/printer.py | 1 + klippy/util.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/klippy/printer.py b/klippy/printer.py index b9724d2510..0bae51cf64 100644 --- a/klippy/printer.py +++ b/klippy/printer.py @@ -666,6 +666,7 @@ def main(): logging.getLogger().setLevel(debuglevel) logging.info("=======================") logging.info("Starting Klippy...") + util.lock_memory() git_info = util.get_git_version() git_vers = git_info["version"] diff --git a/klippy/util.py b/klippy/util.py index 32b5485b30..5c0703bc2c 100644 --- a/klippy/util.py +++ b/klippy/util.py @@ -3,6 +3,8 @@ # Copyright (C) 2016-2020 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. +import ctypes +import ctypes.util import fcntl import json import logging @@ -27,6 +29,24 @@ def fix_sigint(): fix_sigint() +# Lock all current and future memory pages into RAM to prevent swapping +def lock_memory(): + MCL_CURRENT = 1 + MCL_FUTURE = 2 + try: + libc = ctypes.CDLL("libc.so.6", use_errno=True) + result = libc.mlockall(MCL_CURRENT | MCL_FUTURE) + if result != 0: + errno_val = ctypes.get_errno() + raise OSError(errno_val, os.strerror(errno_val)) + logging.info( + "Memory locked to RAM (mlockall MCL_CURRENT|MCL_FUTURE):" + " swapping disabled" + ) + except Exception as e: + logging.warning("Failed to lock memory: %s", e) + + # Set a file-descriptor as non-blocking def set_nonblock(fd): fcntl.fcntl(