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: