From 02a7f2ab71304f85343613dd074efdb16dbe0e68 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 14 May 2026 17:41:42 +0200 Subject: 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. --- python/vyos/utils/commit.py | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) 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 / 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!""" -- cgit v1.2.3 From abd22ba6c3852fbf1002204a416b9de83e405caa Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 15 May 2026 19:44:11 +0200 Subject: vyos-netlinkd: T8781: use faster commit_in_progress2 with order O(1) Move to the re-implementation of the commit in progress check added in commit 002d45b70efd ("vyos.commit: T8781: move from O(n) to O(1) for commit_in_progress() checks"). --- src/services/vyos-netlinkd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/vyos-netlinkd b/src/services/vyos-netlinkd index 368824bf9..addfc8ef9 100755 --- a/src/services/vyos-netlinkd +++ b/src/services/vyos-netlinkd @@ -29,7 +29,7 @@ from typing import Optional from vyos.configquery import op_mode_config_dict from vyos.ifconfig import Section from vyos.utils.boot import boot_configuration_complete -from vyos.utils.commit import commit_in_progress +from vyos.utils.commit import commit_in_progress2 from vyos.utils.dict import dict_search from vyos.utils.process import cmd from vyos.utils.process import is_systemd_service_active @@ -127,8 +127,8 @@ def main(): # Check if a config commit is in progress before processing any # messages. This avoids blocking per-message and reduces unnecessary - # commit_in_progress() calls. - if commit_in_progress(): + # calls to commit_in_progress2() + if commit_in_progress2(): syslog.syslog(syslog.LOG_DEBUG, 'Config commit in progress, skipping netlink events') sleep(1) -- cgit v1.2.3