diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-05-14 17:41:42 +0200 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-05-15 19:46:50 +0200 |
| commit | 02a7f2ab71304f85343613dd074efdb16dbe0e68 (patch) | |
| tree | 5ffab4b6ecea5244b807ba2a76b9d7c0004e3007 | |
| parent | 08542c03c0d1ab2c114966f6fbd4c2b7c3a45b4a (diff) | |
| download | vyos-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.py | 57 |
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!""" |
