Cherry-pick 322149@main (1637027a628d). https://bugs.webkit.org/show_bug.cgi?id=325092 [webkitcorepy] FileLock race can trigger FileNotFoundError checking for stale locks https://bugs.webkit.org/show_bug.cgi?id=325092 Reviewed by Jonathan Bedard and Sam Sneddon. 264321@main moved Unix ports from using fcntl.flock to os.O_EXLOCK, also adding a fallback method with os utilities when os.O_EXLOCK is not available (e.g. Linux). While it worked fine most of the time, under some constraints this fallback method can trigger some race conditions, like files disappearing between checking their existence and actually opening them (e.g. triggering FileNotFoundError when checking for stale lock files). So this commit adds back the fcntl.flock method as an alternative for unix systems when os.O_EXLOCK is not available, while still keeping the current fallback in place. * Tools/Scripts/libraries/webkitcorepy/webkitcorepy/file_lock.py: (FileLock.acquire): (FileLock): (FileLock.release): Canonical link: https://commits.webkit.org/322149@main Canonical link: https://commits.webkit.org/305877.1187@webkitglib/2.52
diff --git a/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/file_lock.py b/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/file_lock.py index 6831934..f2faaa8 100644 --- a/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/file_lock.py +++ b/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/file_lock.py
@@ -32,12 +32,16 @@ if sys.platform.startswith('win'): import msvcrt + fcntl = None +else: + import fcntl class FileLock(object): INTEGER_RE = re.compile(r'^\d+$') USE_WINDOWS = sys.platform.startswith('win') USE_EXLOCK = not USE_WINDOWS and getattr(os, 'O_EXLOCK', False) + USE_FCNTL_FLOCK = not USE_WINDOWS and not USE_EXLOCK and hasattr(fcntl, 'flock') @classmethod def is_process_running(cls, pid): @@ -64,11 +68,14 @@ if self._descriptor: raise RuntimeError('Cannot re-enter acquired FileLock') - if not self.USE_EXLOCK and not self.USE_WINDOWS and os.path.exists(self.path): - with open(self.path) as file: - pid = file.readline().strip() - if self.INTEGER_RE.match(pid) and not self.is_process_running(int(pid)): - os.unlink(self.path) + if not self.USE_EXLOCK and not self.USE_WINDOWS and not self.USE_FCNTL_FLOCK and os.path.exists(self.path): + try: + with open(self.path) as file: + pid = file.readline().strip() + if self.INTEGER_RE.match(pid) and not self.is_process_running(int(pid)): + os.unlink(self.path) + except FileNotFoundError: # Protect from another racing Lock already cleaning up the stale lock + pass if self.USE_EXLOCK and self.timeout: with Timeout( @@ -86,6 +93,14 @@ msvcrt.locking(self._descriptor, msvcrt.LK_NBLCK, 32) elif self.USE_EXLOCK: self._descriptor = os.open(self.path, os.O_CREAT | os.O_WRONLY | os.O_EXLOCK | os.O_NONBLOCK) + elif self.USE_FCNTL_FLOCK: + descriptor = os.open(self.path, os.O_CREAT | os.O_RDONLY, 0o666) + try: + fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) + except (IOError, OSError): + os.close(descriptor) + raise + self._descriptor = descriptor else: self._descriptor = os.open(self.path, os.O_CREAT | os.O_WRONLY | os.O_EXCL) os.write(self._descriptor, string_utils.encode(str(os.getpid()))) @@ -105,6 +120,8 @@ try: if self.USE_WINDOWS: msvcrt.locking(self._descriptor, msvcrt.LK_UNLCK, 32) + elif self.USE_FCNTL_FLOCK: + fcntl.flock(self._descriptor, fcntl.LOCK_UN) elif not self.USE_EXLOCK: os.unlink(self.path) finally: