diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-05-18 17:02:57 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-18 17:02:57 +0200 |
| commit | edccb2fc91163a626ece24c8be14a8bf3d444d8f (patch) | |
| tree | 0d2d243bf800a1f66d1b81b239c173c4eb29c78d | |
| parent | 964a01697f3c2361ce4324fb8333f5fe4d2b0434 (diff) | |
| parent | abd22ba6c3852fbf1002204a416b9de83e405caa (diff) | |
| download | vyos-1x-edccb2fc91163a626ece24c8be14a8bf3d444d8f.tar.gz vyos-1x-edccb2fc91163a626ece24c8be14a8bf3d444d8f.zip | |
Merge pull request #5200 from c-po/commit-in-progress
vyos.commit: T8781: move from O(n) to O(1) for commit_in_progress() checks
| -rw-r--r-- | python/vyos/utils/commit.py | 57 | ||||
| -rwxr-xr-x | src/services/vyos-netlinkd | 6 |
2 files changed, 60 insertions, 3 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!""" 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) |
