summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-05-14 17:41:42 +0200
committerChristian Breunig <christian@breunig.cc>2026-05-15 19:46:50 +0200
commit02a7f2ab71304f85343613dd074efdb16dbe0e68 (patch)
tree5ffab4b6ecea5244b807ba2a76b9d7c0004e3007
parent08542c03c0d1ab2c114966f6fbd4c2b7c3a45b4a (diff)
downloadvyos-1x-02a7f2ab71304f85343613dd074efdb16dbe0e68.tar.gz
vyos-1x-02a7f2ab71304f85343613dd074efdb16dbe0e68.zip
vyos.commit: T8781: move from O(n) to O(1) for commit_in_progress() checks
Historically, commit_in_progress() used psutil.process_iter() to enumerate every process on the system and inspect open file descriptors under /proc in order to determine whether the configuration commit lock was held. That approach scales linearly with process count and incurs substantial overhead under load. Replace it with the shared lock-file utilities: attempt to acquire the commit lock using a non-blocking exclusive lock. If acquisition fails, another holder retains the lock and a commit is considered in progress; if it succeeds, no commit was active and the lock acquired for the probe is released immediately.
-rw-r--r--python/vyos/utils/commit.py57
1 files changed, 57 insertions, 0 deletions
diff --git a/python/vyos/utils/commit.py b/python/vyos/utils/commit.py
index 4147c7fba..1bbb236e4 100644
--- a/python/vyos/utils/commit.py
+++ b/python/vyos/utils/commit.py
@@ -18,6 +18,63 @@
from typing import IO
+def _commit_lock_busy(lock_path: str) -> bool:
+ """Return True if another process holds a POSIX advisory lock on lock_path.
+
+ Uses libc lockf(F_TEST): never acquires or releases a lock (no observer window
+ where this code holds LOCK_EX). Compatible with locks taken via fcntl.lockf /
+ fcntl F_SETLK on Linux.
+ """
+ import ctypes
+ import errno
+ import os
+
+ libc = ctypes.CDLL('libc.so.6', use_errno=True)
+ lockf_fn = libc.lockf
+ lockf_fn.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_long]
+ lockf_fn.restype = ctypes.c_int
+
+ # Defined in glibc <unistd.h> / <fcntl.h> as F_TEST.
+ _F_TEST = 3
+
+ try:
+ fd = os.open(lock_path, os.O_RDONLY)
+ except FileNotFoundError:
+ # Lost a race with unlink or commit teardown.
+ return False
+ try:
+ os.lseek(fd, 0, os.SEEK_SET)
+ ctypes.set_errno(0)
+ ret = lockf_fn(fd, _F_TEST, 0)
+ err = ctypes.get_errno()
+ if ret == 0:
+ return False
+ if err in (errno.EACCES, errno.EAGAIN):
+ return True
+ raise OSError(err, os.strerror(err), lock_path)
+ finally:
+ os.close(fd)
+
+def commit_in_progress2():
+ """
+ Modern implementation of commit_in_progress() which is O(1) instead of O(n)
+
+ The reason not everything is moved to this new implementation yet is to
+ give it heavy testing in vyos-netlinkd first.
+ """
+ # Query advisory locks without acquiring them (see _commit_lock_busy).
+ # Requires read access to the lock file.
+ # If there is no read access otherwise os.open raises PermissionError.
+
+ from pathlib import Path
+ from vyos.defaults import commit_lock
+
+ lock_path = Path(commit_lock)
+ if not lock_path.exists():
+ return False
+
+ return _commit_lock_busy(str(lock_path))
+
def commit_in_progress():
"""Not to be used in normal op mode scripts!"""