From 163e92dd04d527dae897ebe4e5acd27605f8cecc Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 13 Jan 2023 21:19:47 +0100 Subject: [PATCH 1/7] bugfix: thread id must be parsed as hex from lock file name --- src/borg/locking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index 6c4c77826..ae3de7a13 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -197,7 +197,7 @@ class ExclusiveLock: host_pid, thread_str = name.rsplit("-", 1) host, pid_str = host_pid.rsplit(".", 1) pid = int(pid_str) - thread = int(thread_str) + thread = int(thread_str, 16) except ValueError: # Malformed lock name? Or just some new format we don't understand? logger.error("Found malformed lock %s in %s. Please check/fix manually.", name, self.path) From e1bb669400863dab508dd6b40c8ac2b35783a74d Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 13 Jan 2023 19:17:48 +0100 Subject: [PATCH 2/7] use os.replace not os.rename --- src/borg/locking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index ae3de7a13..a98d7b1e2 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -141,7 +141,7 @@ class ExclusiveLock: timer = TimeoutTimer(timeout, sleep).start() while True: try: - os.rename(temp_path, self.path) + os.replace(temp_path, self.path) except OSError: # already locked if self.by_me(): return self From 0ce2b55031226851598926758913b5a3b292d05d Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 13 Jan 2023 19:56:46 +0100 Subject: [PATCH 3/7] fix host, pid, tid order using "differenthost" (== not the current hostname) makes the process_alive check always return True (to play safe, because in can not check for processes on other hosts). --- src/borg/testsuite/locking.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/borg/testsuite/locking.py b/src/borg/testsuite/locking.py index 69a47bb58..0d16d0b00 100644 --- a/src/borg/testsuite/locking.py +++ b/src/borg/testsuite/locking.py @@ -181,11 +181,12 @@ class TestExclusiveLock: exception_counter = SynchronizedCounter() print_lock = ThreadingLock() thread = None + host_id, process_id = "differenthost", 1234 for thread_id in range(RACE_TEST_NUM_THREADS): thread = Thread( target=acquire_release_loop, args=( - ("foo", thread_id, 0), + (host_id, process_id, thread_id), RACE_TEST_DURATION, thread_id, lock_owner_counter, From 7063c2abeccefb79455b1bc035edcb69fcb212d0 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 13 Jan 2023 20:48:50 +0100 Subject: [PATCH 4/7] locking (win32): deal with os.listdir PermissionErrors due to unclear circumstances, windows sometimes just says "PermissionError" when trying to list a directory. --- src/borg/locking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/borg/locking.py b/src/borg/locking.py index a98d7b1e2..efb9e997b 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -191,6 +191,8 @@ class ExclusiveLock: names = os.listdir(self.path) except FileNotFoundError: # another process did our job in the meantime. pass + except PermissionError: # win32 might throw this. + return False else: for name in names: try: From 6a3aa23f9bee0a8443d42c1cb8d1d1162b81d728 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 13 Jan 2023 20:56:52 +0100 Subject: [PATCH 5/7] locking (win32): deal with os.rmdir PermissionErrors Retry if access is denied. --- src/borg/locking.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index efb9e997b..ea6a4423b 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -170,15 +170,22 @@ class ExclusiveLock: if not self.by_me(): raise NotMyLock(self.path) os.unlink(self.unique_name) - try: - os.rmdir(self.path) - except OSError as err: - if err.errno not in (errno.ENOTEMPTY, errno.EEXIST, errno.ENOENT): - # EACCES or EIO or ... = we cannot operate anyway, so re-throw - raise err - # else: - # Directory is not empty or doesn't exist any more. - # this means we lost the race to somebody else -- which is ok. + retry = 0 + while retry < 42: + retry += 1 + try: + os.rmdir(self.path) + except OSError as err: + if err.errno in (errno.EACCES,): + # windows behaving strangely? -> just try again. + continue + if err.errno not in (errno.ENOTEMPTY, errno.EEXIST, errno.ENOENT): + # EACCES or EIO or ... = we cannot operate anyway, so re-throw + raise err + # else: + # Directory is not empty or doesn't exist any more. + # this means we lost the race to somebody else -- which is ok. + return def is_locked(self): return os.path.exists(self.path) From 9ba249b48252f9d69be440d43a69cf30f69257d3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 18 Jan 2023 13:42:39 +0100 Subject: [PATCH 6/7] use for loop --- src/borg/locking.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index ea6a4423b..e9718ebda 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -170,9 +170,7 @@ class ExclusiveLock: if not self.by_me(): raise NotMyLock(self.path) os.unlink(self.unique_name) - retry = 0 - while retry < 42: - retry += 1 + for retry in range(42): try: os.rmdir(self.path) except OSError as err: From c3d0c525f98e67608bf22d3216f64afd019e29fc Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 18 Jan 2023 13:44:51 +0100 Subject: [PATCH 7/7] locking: get out of kill_stale_lock more quickly if directory does not exist. --- src/borg/locking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/borg/locking.py b/src/borg/locking.py index e9718ebda..2d94ab66d 100644 --- a/src/borg/locking.py +++ b/src/borg/locking.py @@ -195,7 +195,7 @@ class ExclusiveLock: try: names = os.listdir(self.path) except FileNotFoundError: # another process did our job in the meantime. - pass + return False except PermissionError: # win32 might throw this. return False else: