commit: d87fa22e70513915cebe79a1923b65ea0a6fc55e Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Wed Mar 25 05:35:17 2020 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Wed Mar 25 07:39:33 2020 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=d87fa22e
lockfile: raise TryAgain if inode is already locked (bug 714480) Raise TryAgain if the current inode is already locked by the current process, since in this case the lock will not behave as intended with the default fcntl.lockf function. With the alternative fcntl.flock function, TryAgain is raised earlier. Bug: https://bugs.gentoo.org/714480 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/portage/locks.py | 12 ++++++++++-- lib/portage/tests/locks/test_lock_nonblock.py | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/portage/locks.py b/lib/portage/locks.py index 2459b12b6..71321e769 100644 --- a/lib/portage/locks.py +++ b/lib/portage/locks.py @@ -89,9 +89,17 @@ _open_inodes = {} class _lock_manager(object): __slots__ = ('fd', 'inode_key') - def __init__(self, fd, fstat_result): + def __init__(self, fd, fstat_result, path): self.fd = fd self.inode_key = (fstat_result.st_dev, fstat_result.st_ino) + if self.inode_key in _open_inodes: + # This means that the lock is already held by the current + # process, so the caller will have to try again. This case + # is encountered with the default fcntl.lockf function, and + # with the alternative fcntl.flock function TryAgain is + # raised earlier. + os.close(fd) + raise TryAgain(path) _open_fds[fd] = self _open_inodes[self.inode_key] = self def close(self): @@ -336,7 +344,7 @@ def _lockfile_iteration(mypath, wantnewlockfile=False, unlinkfile=False, fcntl.fcntl(myfd, fcntl.F_SETFD, fcntl.fcntl(myfd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC) - _lock_manager(myfd, os.fstat(myfd) if fstat_result is None else fstat_result) + _lock_manager(myfd, os.fstat(myfd) if fstat_result is None else fstat_result, mypath) writemsg(str((lockfilename, myfd, unlinkfile)) + "\n", 1) return (lockfilename, myfd, unlinkfile, locking_method) diff --git a/lib/portage/tests/locks/test_lock_nonblock.py b/lib/portage/tests/locks/test_lock_nonblock.py index 2ff7b3527..02ba16ad9 100644 --- a/lib/portage/tests/locks/test_lock_nonblock.py +++ b/lib/portage/tests/locks/test_lock_nonblock.py @@ -1,4 +1,4 @@ -# Copyright 2011 Gentoo Foundation +# Copyright 2011-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import tempfile @@ -7,6 +7,7 @@ import traceback import portage from portage import os from portage import shutil +from portage.exception import TryAgain from portage.tests import TestCase class LockNonblockTestCase(TestCase): @@ -60,3 +61,16 @@ class LockNonblockTestCase(TestCase): if prev_state is not None: os.environ["__PORTAGE_TEST_HARDLINK_LOCKS"] = prev_state + def test_competition_with_same_process(self): + """ + Test that at attempt to lock the same file multiple times in the + same process will behave as intended (bug 714480). + """ + tempdir = tempfile.mkdtemp() + try: + path = os.path.join(tempdir, 'lock_me') + lock = portage.locks.lockfile(path) + self.assertRaises(TryAgain, portage.locks.lockfile, path, flags=os.O_NONBLOCK) + portage.locks.unlockfile(lock) + finally: + shutil.rmtree(tempdir)
