xref: /aosp_15_r20/external/cronet/build/fuchsia/test/lockfile.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Exclusive filelocking for all supported platforms.
5
6Copied from third_party/depot_tools/lockfile.py.
7"""
8
9import contextlib
10import fcntl
11import logging
12import os
13import time
14
15
16class LockError(Exception):
17    """Error raised if timeout or lock (without timeout) fails."""
18
19
20def _open_file(lockfile):
21    open_flags = (os.O_CREAT | os.O_WRONLY)
22    return os.open(lockfile, open_flags, 0o644)
23
24
25def _close_file(file_descriptor):
26    os.close(file_descriptor)
27
28
29def _lock_file(file_descriptor):
30    fcntl.flock(file_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
31
32
33def _try_lock(lockfile):
34    f = _open_file(lockfile)
35    try:
36        _lock_file(f)
37    except Exception:
38        _close_file(f)
39        raise
40    return lambda: _close_file(f)
41
42
43def _lock(path, timeout=0):
44    """_lock returns function to release the lock if locking was successful.
45
46    _lock also implements simple retry logic."""
47    elapsed = 0
48    while True:
49        try:
50            return _try_lock(path + '.locked')
51        except (OSError, IOError) as error:
52            if elapsed < timeout:
53                sleep_time = min(10, timeout - elapsed)
54                logging.info(
55                    'Could not create lockfile; will retry after sleep(%d).',
56                    sleep_time)
57                elapsed += sleep_time
58                time.sleep(sleep_time)
59                continue
60            raise LockError("Error locking %s (err: %s)" %
61                            (path, str(error))) from error
62
63
64@contextlib.contextmanager
65def lock(path, timeout=0):
66    """Get exclusive lock to path.
67
68    Usage:
69        import lockfile
70        with lockfile.lock(path, timeout):
71            # Do something
72            pass
73
74     """
75    release_fn = _lock(path, timeout)
76    try:
77        yield
78    finally:
79        release_fn()
80