============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-9.0.2, pluggy-1.6.0
rootdir: /workspace/portalocker
configfile: pytest.ini
plugins: anyio-4.12.1, metadata-3.1.1, json-report-1.5.0, cov-7.0.0
collected 40 items / 2 errors

portalocker_tests/temporary_file_lock.py F                               [  2%]
portalocker_tests/test_combined.py F                                     [  5%]
portalocker_tests/test_semaphore.py FFFFFFFFF                            [ 27%]
portalocker_tests/tests.py ................FF...F.....F.
ERROR: Coverage failure: total of 78 is less than fail-under=100
                                                                         [100%]

==================================== ERRORS ====================================
_______________ ERROR collecting portalocker_tests/test_redis.py _______________
ImportError while importing test module '/workspace/portalocker/portalocker_tests/test_redis.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.10/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
portalocker_tests/test_redis.py:9: in <module>
    from portalocker import redis, utils
portalocker/redis.py:7: in <module>
    from redis import client
E   ModuleNotFoundError: No module named 'redis'
_______________ ERROR collecting portalocker_tests/test_redis.py _______________
ImportError while importing test module '/workspace/portalocker/portalocker_tests/test_redis.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.10/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
portalocker_tests/test_redis.py:9: in <module>
    from portalocker import redis, utils
portalocker/redis.py:7: in <module>
    from redis import client
E   ModuleNotFoundError: No module named 'redis'
=================================== FAILURES ===================================
___________________________ test_temporary_file_lock ___________________________

tmpfile = '/tmp/pytest-of-openhands/pytest-11/test_temporary_file_lock0/027164331965898514'

    def test_temporary_file_lock(tmpfile):
        with portalocker.TemporaryFileLock(tmpfile):
            pass
    
>       assert not os.path.isfile(tmpfile)
E       AssertionError: assert not True
E        +  where True = <function isfile at 0x7f1b027ddf30>('/tmp/pytest-of-openhands/pytest-11/test_temporary_file_lock0/027164331965898514')
E        +    where <function isfile at 0x7f1b027ddf30> = <module 'posixpath' from '/usr/lib/python3.10/posixpath.py'>.isfile
E        +      where <module 'posixpath' from '/usr/lib/python3.10/posixpath.py'> = os.path

portalocker_tests/temporary_file_lock.py:10: AssertionError
________________________________ test_combined _________________________________

tmpdir = local('/tmp/pytest-of-openhands/pytest-11/test_combined0')

    def test_combined(tmpdir):
        output_file = tmpdir.join('combined.py')
        __main__.main(['combine', '--output-file', output_file.strpath])
    
        print(output_file)  # noqa: T201
        print('#################')  # noqa: T201
        print(output_file.read())  # noqa: T201
        print('#################')  # noqa: T201
    
        sys.path.append(output_file.dirname)
        # Combined is being generated above but linters won't understand that
>       import combined  # type: ignore
E         File "/tmp/pytest-of-openhands/pytest-11/test_combined0/combined.py", line 255
E           UNBLOCK = LOCK_UNimport typing
E                                   ^^^^^^
E       SyntaxError: invalid syntax

portalocker_tests/test_combined.py:17: SyntaxError
----------------------------- Captured stdout call -----------------------------
/tmp/pytest-of-openhands/pytest-11/test_combined0/combined.py
#################
'''
############################################
portalocker - Cross-platform locking library
############################################

.. image:: https://github.com/WoLpH/portalocker/actions/workflows/python-package.yml/badge.svg?branch=master
    :alt: Linux Test Status
    :target: https://github.com/WoLpH/portalocker/actions/

.. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
    :alt: Windows Tests Status
    :target: https://ci.appveyor.com/project/WoLpH/portalocker

.. image:: https://coveralls.io/repos/WoLpH/portalocker/badge.svg?branch=master
    :alt: Coverage Status
    :target: https://coveralls.io/r/WoLpH/portalocker?branch=master

Overview
--------

Portalocker is a library to provide an easy API to file locking.

An important detail to note is that on Linux and Unix systems the locks are
advisory by default. By specifying the `-o mand` option to the mount command it
is possible to enable mandatory file locking on Linux. This is generally not
recommended however. For more information about the subject:

 - https://en.wikipedia.org/wiki/File_locking
 - http://stackoverflow.com/questions/39292051/portalocker-does-not-seem-to-lock
 - https://stackoverflow.com/questions/12062466/mandatory-file-lock-on-linux

The module is currently maintained by Rick van Hattem <Wolph@wol.ph>.
The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
requests can be submitted there. Patches are also very welcome.

Security contact information
------------------------------------------------------------------------------

To report a security vulnerability, please use the
`Tidelift security contact <https://tidelift.com/security>`_.
Tidelift will coordinate the fix and disclosure.

Redis Locks
-----------

This library now features a lock based on Redis which allows for locks across
multiple threads, processes and even distributed locks across multiple
computers.

It is an extremely reliable Redis lock that is based on pubsub.

As opposed to most Redis locking systems based on key/value pairs,
this locking method is based on the pubsub system. The big advantage is
that if the connection gets killed due to network issues, crashing
processes or otherwise, it will still immediately unlock instead of
waiting for a lock timeout.

First make sure you have everything installed correctly:

::

    pip install "portalocker[redis]"

Usage is really easy:

::

    import portalocker

    lock = portalocker.RedisLock('some_lock_channel_name')

    with lock:
        print('do something here')

The API is essentially identical to the other ``Lock`` classes so in addition
to the ``with`` statement you can also use ``lock.acquire(...)``.

Python 2
--------

Python 2 was supported in versions before Portalocker 2.0. If you are still
using
Python 2,
you can run this to install:

::

    pip install "portalocker<2"

Tips
----

On some networked filesystems it might be needed to force a `os.fsync()` before
closing the file so it's actually written before another client reads the file.
Effectively this comes down to:

::

   with portalocker.Lock('some_file', 'rb+', timeout=60) as fh:
       # do what you need to do
       ...

       # flush and sync to filesystem
       fh.flush()
       os.fsync(fh.fileno())

Links
-----

* Documentation
    - http://portalocker.readthedocs.org/en/latest/
* Source
    - https://github.com/WoLpH/portalocker
* Bug reports
    - https://github.com/WoLpH/portalocker/issues
* Package homepage
    - https://pypi.python.org/pypi/portalocker
* My blog
    - http://w.wol.ph/

Examples
--------

To make sure your cache generation scripts don't race, use the `Lock` class:

>>> import portalocker
>>> with portalocker.Lock('somefile', timeout=1) as fh:
...     print('writing some stuff to my cache...', file=fh)

To customize the opening and locking a manual approach is also possible:

>>> import portalocker
>>> file = open('somefile', 'r+')
>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
>>> file.seek(12)
>>> file.write('foo')
>>> file.close()

Explicitly unlocking is not needed in most cases but omitting it has been known
to cause issues:
https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266

If needed, it can be done through:

>>> portalocker.unlock(file)

Do note that your data might still be in a buffer so it is possible that your
data is not available until you `flush()` or `close()`.

To create a cross platform bounded semaphore across multiple processes you can
use the `BoundedSemaphore` class which functions somewhat similar to
`threading.BoundedSemaphore`:

>>> import portalocker
>>> n = 2
>>> timeout = 0.1

>>> semaphore_a = portalocker.BoundedSemaphore(n, timeout=timeout)
>>> semaphore_b = portalocker.BoundedSemaphore(n, timeout=timeout)
>>> semaphore_c = portalocker.BoundedSemaphore(n, timeout=timeout)

>>> semaphore_a.acquire()
<portalocker.utils.Lock object at ...>
>>> semaphore_b.acquire()
<portalocker.utils.Lock object at ...>
>>> semaphore_c.acquire()
Traceback (most recent call last):
  ...
portalocker.exceptions.AlreadyLocked


More examples can be found in the
`tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.


Versioning
----------

This library follows `Semantic Versioning <http://semver.org/>`_.


Changelog
---------

Every release has a ``git tag`` with a commit message for the tag
explaining what was added and/or changed. The list of tags/releases
including the commit messages can be found here:
https://github.com/WoLpH/portalocker/releases

License
-------

See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.


'''

'''
Copyright 2022 Rick van Hattem

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''

__package_name__ = 'portalocker'
__author__ = 'Rick van Hattem'
__email__ = 'wolph@wol.ph'
__version__ = '2.10.1'
__description__ = 'Wraps the portalocker recipe for easy usage'
__url__ = 'https://github.com/WoLpH/portalocker'"""
Locking constants

Lock types:

- `EXCLUSIVE` exclusive lock
- `SHARED` shared lock

Lock flags:

- `NON_BLOCKING` non-blocking

Manually unlock, only needed internally

- `UNBLOCK` unlock
"""
import enum
import os
if os.name == 'nt':
    import msvcrt
    LOCK_EX = 1
    LOCK_SH = 2
    LOCK_NB = 4
    LOCK_UN = msvcrt.LK_UNLCK
elif os.name == 'posix':
    import fcntl
    LOCK_EX = fcntl.LOCK_EX
    LOCK_SH = fcntl.LOCK_SH
    LOCK_NB = fcntl.LOCK_NB
    LOCK_UN = fcntl.LOCK_UN
else:
    raise RuntimeError('PortaLocker only defined for nt and posix platforms')

class LockFlags(enum.IntFlag):
    EXCLUSIVE = LOCK_EX
    SHARED = LOCK_SH
    NON_BLOCKING = LOCK_NB
    UNBLOCK = LOCK_UNimport typing

class BaseLockException(Exception):
    LOCK_FAILED = 1

    def __init__(self, *args: typing.Any, fh: typing.Union[typing.IO, None, int]=None, **kwargs: typing.Any) -> None:
        self.fh = fh
        Exception.__init__(self, *args)

class LockException(BaseLockException):
    pass

class AlreadyLocked(LockException):
    pass

class FileToLarge(LockException):
    passimport os
import typing

class HasFileno(typing.Protocol):
    def fileno(self) -> int:
        ...

LOCKER: typing.Optional[typing.Callable[[typing.Union[int, HasFileno], int], typing.Any]] = None
if os.name == 'nt':
    import msvcrt
    import pywintypes
    import win32con
    import win32file
    import winerror
    __overlapped = pywintypes.OVERLAPPED()
    
    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using Windows locking mechanisms."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
        
        # Convert flags to Windows equivalents
        win_flags = 0
        if flags & LOCK_EX:
            win_flags |= win32con.LOCKFILE_EXCLUSIVE_LOCK
        if flags & LOCK_NB:
            win_flags |= win32con.LOCKFILE_FAIL_IMMEDIATELY
        if flags & LOCK_UN:
            win_flags = win32con.LOCKFILE_UNLOCK
        
        try:
            win32file.LockFileEx(
                win32file._get_osfhandle(fd),
                win_flags,
                0,
                0xFFFFFFFF,
                __overlapped
            )
        except pywintypes.error as e:
            if e.winerror == winerror.ERROR_LOCK_VIOLATION:
                raise AlreadyLocked("File is already locked") from e
            raise LockException("Failed to lock file") from e
    
    def unlock(file: typing.Union[int, HasFileno]) -> None:
        """Unlock a file using Windows locking mechanisms."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
            
        try:
            win32file.UnlockFileEx(
                win32file._get_osfhandle(fd),
                0,
                0xFFFFFFFF,
                __overlapped
            )
        except pywintypes.error as e:
            raise LockException("Failed to unlock file") from e
            
elif os.name == 'posix':
    import errno
    import fcntl
    
    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
        
        try:
            fcntl.flock(fd, flags)
        except OSError as e:
            if e.errno == errno.EAGAIN or e.errno == errno.EACCES:
                raise AlreadyLocked("File is already locked") from e
            raise LockException("Failed to lock file") from e
    
    def unlock(file: typing.Union[int, HasFileno]) -> None:
        """Unlock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
        
        try:
            fcntl.flock(fd, LOCK_UN)
        except OSError as e:
            raise LockException("Failed to unlock file") from e
    
    LOCKER = fcntl.flock
else:
    raise RuntimeError('PortaLocker only defined for nt and posix platforms')import abc
import atexit
import contextlib
import logging
import os
import pathlib
import random
import tempfile
import time
import typing
import warnings
logger = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 5
DEFAULT_CHECK_INTERVAL = 0.25
DEFAULT_FAIL_WHEN_LOCKED = False
LOCK_METHOD = LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING
__all__ = ['Lock', 'open_atomic']
Filename = typing.Union[str, pathlib.Path]

def coalesce(*args: typing.Any, test_value: typing.Any=None) -> typing.Any:
    """Simple coalescing function that returns the first value that is not
    equal to the `test_value`. Or `None` if no value is valid. Usually this
    means that the last given value is the default value.

    Note that the `test_value` is compared using an identity check
    (i.e. `value is not test_value`) so changing the `test_value` won't work
    for all values.

    >>> coalesce(None, 1)
    1
    >>> coalesce()

    >>> coalesce(0, False, True)
    0
    >>> coalesce(0, False, True, test_value=0)
    False

    # This won't work because of the `is not test_value` type testing:
    >>> coalesce([], dict(spam='eggs'), test_value=[])
    []
    """
    for arg in args:
        if arg is not test_value:
            return arg
    return None

@contextlib.contextmanager
def open_atomic(filename: Filename, binary: bool=True) -> typing.Iterator[typing.IO]:
    """Open a file for atomic writing. Instead of locking this method allows
    you to write the entire file and move it to the actual location. Note that
    this makes the assumption that a rename is atomic on your platform which
    is generally the case but not a guarantee.

    http://docs.python.org/library/os.html#os.rename

    >>> filename = 'test_file.txt'
    >>> if os.path.exists(filename):
    ...     os.remove(filename)

    >>> with open_atomic(filename) as fh:
    ...     written = fh.write(b'test')
    >>> assert os.path.exists(filename)
    >>> os.remove(filename)

    >>> import pathlib
    >>> path_filename = pathlib.Path('test_file.txt')

    >>> with open_atomic(path_filename) as fh:
    ...     written = fh.write(b'test')
    >>> assert path_filename.exists()
    >>> path_filename.unlink()
    """
    # Create a temporary file in the same directory
    if isinstance(filename, pathlib.Path):
        temp_dir = str(filename.parent)
        temp_prefix = str(filename.name) + '.tmp.'
    else:
        temp_dir = os.path.dirname(filename) or '.'
        temp_prefix = os.path.basename(filename) + '.tmp.'
    
    # Create temporary file
    temp_fd, temp_path = tempfile.mkstemp(suffix='', prefix=temp_prefix, dir=temp_dir)
    
    try:
        # Open the temporary file
        if binary:
            temp_file = os.fdopen(temp_fd, 'wb')
        else:
            temp_file = os.fdopen(temp_fd, 'w')
        
        yield temp_file
        
        # Close the temporary file to ensure all data is written
        temp_file.close()
        
        # Atomically move the temporary file to the target location
        os.replace(temp_path, filename)
        
    except Exception:
        # Clean up the temporary file on error
        os.close(temp_fd)
        os.unlink(temp_path)
        raise

class LockBase(abc.ABC):
    timeout: float
    check_interval: float
    fail_when_locked: bool

    def __init__(self, timeout: typing.Optional[float]=None, check_interval: typing.Optional[float]=None, fail_when_locked: typing.Optional[bool]=None):
        self.timeout = coalesce(timeout, DEFAULT_TIMEOUT)
        self.check_interval = coalesce(check_interval, DEFAULT_CHECK_INTERVAL)
        self.fail_when_locked = coalesce(fail_when_locked, DEFAULT_FAIL_WHEN_LOCKED)

    def __enter__(self) -> typing.IO[typing.AnyStr]:
        return self.acquire()

    def __exit__(self, exc_type: typing.Optional[typing.Type[BaseException]], exc_value: typing.Optional[BaseException], traceback: typing.Any) -> typing.Optional[bool]:
        self.release()
        return None

    def __delete__(self, instance):
        instance.release()

class Lock(LockBase):
    """Lock manager with built-in timeout

    Args:
        filename: filename
        mode: the open mode, 'a' or 'ab' should be used for writing. When mode
            contains `w` the file will be truncated to 0 bytes.
        timeout: timeout when trying to acquire a lock
        check_interval: check interval while waiting
        fail_when_locked: after the initial lock failed, return an error
            or lock the file. This does not wait for the timeout.
        **file_open_kwargs: The kwargs for the `open(...)` call

    fail_when_locked is useful when multiple threads/processes can race
    when creating a file. If set to true than the system will wait till
    the lock was acquired and then return an AlreadyLocked exception.

    Note that the file is opened first and locked later. So using 'w' as
    mode will result in truncate _BEFORE_ the lock is checked.
    """

    def __init__(self, filename: Filename, mode: str='a', timeout: typing.Optional[float]=None, check_interval: float=DEFAULT_CHECK_INTERVAL, fail_when_locked: bool=DEFAULT_FAIL_WHEN_LOCKED, flags: LockFlags=LOCK_METHOD, **file_open_kwargs):
        if 'w' in mode:
            truncate = True
            mode = mode.replace('w', 'a')
        else:
            truncate = False
        if timeout is None:
            timeout = DEFAULT_TIMEOUT
        elif not flags & LockFlags.NON_BLOCKING:
            warnings.warn('timeout has no effect in blocking mode', stacklevel=1)
        self.fh: typing.Optional[typing.IO] = None
        self.filename: str = str(filename)
        self.mode: str = mode
        self.truncate: bool = truncate
        self.timeout: float = timeout
        self.check_interval: float = check_interval
        self.fail_when_locked: bool = fail_when_locked
        self.flags: LockFlags = flags
        self.file_open_kwargs = file_open_kwargs

    def acquire(self, timeout: typing.Optional[float]=None, check_interval: typing.Optional[float]=None, fail_when_locked: typing.Optional[bool]=None) -> typing.IO[typing.AnyStr]:
        """Acquire the locked filehandle"""
        timeout = coalesce(timeout, self.timeout)
        check_interval = coalesce(check_interval, self.check_interval)
        fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
        
        # Issue warning if timeout is used with blocking mode
        if timeout is not None and not self.flags & LockFlags.NON_BLOCKING:
            warnings.warn('timeout has no effect in blocking mode', stacklevel=1)
        
        # If we already have a file handle, just return it
        if self.fh is not None:
            return self.fh
            
        # Try to acquire the lock
        start_time = time.time()
        while True:
            try:
                # Get a new file handle
                self.fh = self._get_fh()
                
                # Try to lock it
                self._get_lock(self.fh)
                
                # Prepare the file handle for use
                self._prepare_fh(self.fh)
                
                return self.fh
                
            except AlreadyLocked:
                # File is already locked
                if fail_when_locked:
                    # If we should fail when locked, raise the exception
                    raise
                
                # Check if we've timed out
                if timeout is not None and time.time() - start_time > timeout:
                    raise LockException("Timeout waiting for lock")
                
                # Wait and try again
                time.sleep(check_interval)
                
            except Exception:
                # Some other error occurred, close the file handle and re-raise
                if self.fh is not None:
                    try:
                        self.fh.close()
                    except Exception:
                        pass
                    self.fh = None
                raise

    def __enter__(self) -> typing.IO[typing.AnyStr]:
        return self.acquire()

    def release(self):
        """Releases the currently locked file handle"""
        if self.fh is not None:
            try:
                # Unlock the file
                unlock(self.fh)
            except Exception:
                # Even if unlocking fails, we still want to close the file
                pass
            finally:
                # Close the file handle
                self.fh.close()
                self.fh = None

    def _get_fh(self) -> typing.IO:
        """Get a new filehandle"""
        # Open the file with the specified mode
        return open(self.filename, self.mode, **self.file_open_kwargs)

    def _get_lock(self, fh: typing.IO) -> typing.IO:
        """
        Try to lock the given filehandle

        returns LockException if it fails"""
        try:
            lock(fh, self.flags)
            return fh
        except AlreadyLocked:
            raise
        except Exception as e:
            raise LockException("Failed to lock file") from e

    def _prepare_fh(self, fh: typing.IO) -> typing.IO:
        """
        Prepare the filehandle for usage

        If truncate is a number, the file will be truncated to that amount of
        bytes
        """
        if self.truncate:
            fh.truncate(0)
        return fh

class RLock(Lock):
    """
    A reentrant lock, functions in a similar way to threading.RLock in that it
    can be acquired multiple times.  When the corresponding number of release()
    calls are made the lock will finally release the underlying file lock.
    """

    def __init__(self, filename, mode='a', timeout=DEFAULT_TIMEOUT, check_interval=DEFAULT_CHECK_INTERVAL, fail_when_locked=False, flags=LOCK_METHOD):
        super().__init__(filename, mode, timeout, check_interval, fail_when_locked, flags)
        self._acquire_count = 0

    def acquire(self, timeout: typing.Optional[float]=None, check_interval: typing.Optional[float]=None, fail_when_locked: typing.Optional[bool]=None) -> typing.IO[typing.AnyStr]:
        """Acquire the reentrant lock"""
        timeout = coalesce(timeout, self.timeout)
        check_interval = coalesce(check_interval, self.check_interval)
        fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
        
        # If we already have a file handle, increment the acquire count and return
        if self.fh is not None:
            self._acquire_count += 1
            return self.fh
            
        # Otherwise, acquire normally
        # But we need to make sure we warn about timeout in blocking mode
        if not self.flags & LockFlags.NON_BLOCKING and timeout is not None:
            warnings.warn('timeout has no effect in blocking mode', stacklevel=1)
        
        # Call the parent acquire to get the file handle
        result = super().acquire(timeout, check_interval, fail_when_locked)
        # Increment the acquire count after successful acquisition
        self._acquire_count += 1
        return result

    def release(self):
        """Release the reentrant lock"""
        if self.fh is None:
            raise LockException("Release called on unlocked lock")
            
        self._acquire_count -= 1
        
        # Only actually release the underlying lock when count reaches 0
        if self._acquire_count <= 0:
            self._acquire_count = 0
            super().release()
        # If we still have acquire count, the file handle is still valid

class TemporaryFileLock(Lock):

    def __init__(self, filename='.lock', timeout=DEFAULT_TIMEOUT, check_interval=DEFAULT_CHECK_INTERVAL, fail_when_locked=True, flags=LOCK_METHOD):
        Lock.__init__(self, filename=filename, mode='w', timeout=timeout, check_interval=check_interval, fail_when_locked=fail_when_locked, flags=flags)
        atexit.register(self.release)

class BoundedSemaphore(LockBase):
    """
    Bounded semaphore to prevent too many parallel processes from running

    This method is deprecated because multiple processes that are completely
    unrelated could end up using the same semaphore.  To prevent this,
    use `NamedBoundedSemaphore` instead. The
    `NamedBoundedSemaphore` is a drop-in replacement for this class.

    >>> semaphore = BoundedSemaphore(2, directory='')
    >>> str(semaphore.get_filenames()[0])
    'bounded_semaphore.00.lock'
    >>> str(sorted(semaphore.get_random_filenames())[1])
    'bounded_semaphore.01.lock'
    """
    lock: typing.Optional[Lock]

    def __init__(self, maximum: int, name: str='bounded_semaphore', filename_pattern: str='{name}.{number:02d}.lock', directory: str=tempfile.gettempdir(), timeout: typing.Optional[float]=DEFAULT_TIMEOUT, check_interval: typing.Optional[float]=DEFAULT_CHECK_INTERVAL, fail_when_locked: typing.Optional[bool]=True):
        self.maximum = maximum
        self.name = name
        self.filename_pattern = filename_pattern
        self.directory = directory
        self.lock: typing.Optional[Lock] = None
        super().__init__(timeout=timeout, check_interval=check_interval, fail_when_locked=fail_when_locked)
        if not name or name == 'bounded_semaphore':
            warnings.warn('`BoundedSemaphore` without an explicit `name` argument is deprecated, use NamedBoundedSemaphore', DeprecationWarning, stacklevel=1)

class NamedBoundedSemaphore(BoundedSemaphore):
    """
    Bounded semaphore to prevent too many parallel processes from running

    It's also possible to specify a timeout when acquiring the lock to wait
    for a resource to become available.  This is very similar to
    `threading.BoundedSemaphore` but works across multiple processes and across
    multiple operating systems.

    Because this works across multiple processes it's important to give the
    semaphore a name.  This name is used to create the lock files.  If you
    don't specify a name, a random name will be generated.  This means that
    you can't use the same semaphore in multiple processes unless you pass the
    semaphore object to the other processes.

    >>> semaphore = NamedBoundedSemaphore(2, name='test')
    >>> str(semaphore.get_filenames()[0])
    '...test.00.lock'

    >>> semaphore = NamedBoundedSemaphore(2)
    >>> 'bounded_semaphore' in str(semaphore.get_filenames()[0])
    True

    """

    def __init__(self, maximum: int, name: typing.Optional[str]=None, filename_pattern: str='{name}.{number:02d}.lock', directory: str=tempfile.gettempdir(), timeout: typing.Optional[float]=DEFAULT_TIMEOUT, check_interval: typing.Optional[float]=DEFAULT_CHECK_INTERVAL, fail_when_locked: typing.Optional[bool]=True):
        if name is None:
            name = 'bounded_semaphore.%d' % random.randint(0, 1000000)
        super().__init__(maximum, name, filename_pattern, directory, timeout, check_interval, fail_when_locked)
try:  # pragma: no cover
    from .redis import RedisLock
except ImportError:  # pragma: no cover
    RedisLock = None  # type: ignore


#: The package name on Pypi
#: Current author and maintainer, view the git history for the previous ones
#: Current author's email address
#: Version number
__version__ = '2.10.1'
#: Package description for Pypi
#: Package homepage


#: Exception thrown when the file is already locked by someone else
#: Exception thrown if an error occurred during locking


#: Lock a file. Note that this is an advisory lock on Linux/Unix systems
#: Unlock a file

#: Place an exclusive lock.
#: Only one process may hold an exclusive lock for a given file at a given
#: time.
LOCK_EX: LockFlags = LockFlags.EXCLUSIVE

#: Place a shared lock.
#: More than one process may hold a shared lock for a given file at a given
#: time.
LOCK_SH: LockFlags = LockFlags.SHARED

#: Acquire the lock in a non-blocking fashion.
LOCK_NB: LockFlags = LockFlags.NON_BLOCKING

#: Remove an existing lock held by this process.
LOCK_UN: LockFlags = LockFlags.UNBLOCK

#: Locking flags enum

#: Locking utility class to automatically handle opening with timeouts and
#: context wrappers

__all__ = [
    'lock',
    'unlock',
    'LOCK_EX',
    'LOCK_SH',
    'LOCK_NB',
    'LOCK_UN',
    'LockFlags',
    'LockException',
    'Lock',
    'RLock',
    'AlreadyLocked',
    'BoundedSemaphore',
    'TemporaryFileLock',
    'open_atomic',
    'RedisLock',
]

#################
----------------------------- Captured stderr call -----------------------------
sh: 1: black: Permission denied
sh: 1: ruff: Permission denied
  File "/tmp/pytest-of-openhands/pytest-11/test_combined0/combined.py", line 255
    UNBLOCK = LOCK_UNimport typing
                            ^^^^^^
SyntaxError: invalid syntax
______________________ test_bounded_semaphore[None-None] _______________________

timeout = None, check_interval = None
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00d0af80>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
________________________ test_bounded_semaphore[None-0] ________________________

timeout = 0, check_interval = None
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00d08850>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
______________________ test_bounded_semaphore[None-0.001] ______________________

timeout = 0.001, check_interval = None
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00cf3970>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
________________________ test_bounded_semaphore[0-None] ________________________

timeout = None, check_interval = 0
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00d09450>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
_________________________ test_bounded_semaphore[0-0] __________________________

timeout = 0, check_interval = 0
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00d081f0>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
_______________________ test_bounded_semaphore[0-0.001] ________________________

timeout = 0.001, check_interval = 0
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00cf05b0>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
_____________________ test_bounded_semaphore[0.0005-None] ______________________

timeout = None, check_interval = 0.0005
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00d0abc0>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
_______________________ test_bounded_semaphore[0.0005-0] _______________________

timeout = 0, check_interval = 0.0005
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00cf1b40>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
_____________________ test_bounded_semaphore[0.0005-0.001] _____________________

timeout = 0.001, check_interval = 0.0005
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f1b00cf3e50>

    @pytest.mark.parametrize('timeout', [None, 0, 0.001])
    @pytest.mark.parametrize('check_interval', [None, 0, 0.0005])
    def test_bounded_semaphore(timeout, check_interval, monkeypatch):
        n = 2
        name: str = str(random.random())
        monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.0001)
        monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.0005)
    
        semaphore_a = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_b = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
        semaphore_c = portalocker.BoundedSemaphore(n, name=name, timeout=timeout)
    
>       semaphore_a.acquire(timeout=timeout)
E       AttributeError: 'BoundedSemaphore' object has no attribute 'acquire'

portalocker_tests/test_semaphore.py:21: AttributeError
___________________________ test_nonblocking[flock] ____________________________

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_nonblocking_flock_0/18526130513733408' mode='w' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING: 4>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
>           fcntl.flock(fd, flags)
E           OSError: [Errno 22] Invalid argument

portalocker/portalocker.py:77: OSError

The above exception was the direct cause of the following exception:

tmpfile = '/tmp/pytest-of-openhands/pytest-11/test_nonblocking_flock_0/18526130513733408'
locker = <built-in function flock>

    @pytest.mark.skipif(
        os.name == 'nt',
        reason='Windows uses an entirely different lockmechanism',
    )
    @pytest.mark.parametrize('locker', LOCKERS, indirect=True)
    def test_nonblocking(tmpfile, locker):
        with open(tmpfile, 'w') as fh, pytest.raises(RuntimeError):
>           portalocker.lock(fh, LockFlags.NON_BLOCKING)

portalocker_tests/tests.py:252: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_nonblocking_flock_0/18526130513733408' mode='w' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING: 4>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
            fcntl.flock(fd, flags)
        except OSError as e:
            if e.errno == errno.EAGAIN or e.errno == errno.EACCES:
                raise exceptions.AlreadyLocked("File is already locked") from e
>           raise exceptions.LockException("Failed to lock file") from e
E           portalocker.exceptions.LockException: Failed to lock file

portalocker/portalocker.py:81: LockException
___________________________ test_nonblocking[lockf] ____________________________

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_nonblocking_lockf_0/5000816989967716' mode='w' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING: 4>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
>           fcntl.flock(fd, flags)
E           OSError: [Errno 22] Invalid argument

portalocker/portalocker.py:77: OSError

The above exception was the direct cause of the following exception:

tmpfile = '/tmp/pytest-of-openhands/pytest-11/test_nonblocking_lockf_0/5000816989967716'
locker = <built-in function lockf>

    @pytest.mark.skipif(
        os.name == 'nt',
        reason='Windows uses an entirely different lockmechanism',
    )
    @pytest.mark.parametrize('locker', LOCKERS, indirect=True)
    def test_nonblocking(tmpfile, locker):
        with open(tmpfile, 'w') as fh, pytest.raises(RuntimeError):
>           portalocker.lock(fh, LockFlags.NON_BLOCKING)

portalocker_tests/tests.py:252: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_nonblocking_lockf_0/5000816989967716' mode='w' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING: 4>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
            fcntl.flock(fd, flags)
        except OSError as e:
            if e.errno == errno.EAGAIN or e.errno == errno.EACCES:
                raise exceptions.AlreadyLocked("File is already locked") from e
>           raise exceptions.LockException("Failed to lock file") from e
E           portalocker.exceptions.LockException: Failed to lock file

portalocker/portalocker.py:81: LockException
____________________ test_exclusive_processes[flock-False] _____________________

tmpfile = '/tmp/pytest-of-openhands/pytest-11/test_exclusive_processes_flock1/6461274342521995'
fail_when_locked = False, locker = <built-in function flock>

    @pytest.mark.parametrize('fail_when_locked', [True, False])
    @pytest.mark.parametrize('locker', LOCKERS, indirect=True)
    def test_exclusive_processes(tmpfile: str, fail_when_locked: bool, locker):
        flags = LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING
    
        print('Locking', tmpfile, fail_when_locked, locker)
        with multiprocessing.Pool(processes=2) as pool:
            # Submit tasks individually
            result_a = pool.apply_async(lock, [tmpfile, fail_when_locked, flags])
            result_b = pool.apply_async(lock, [tmpfile, fail_when_locked, flags])
    
            try:
                a = result_a.get(timeout=1.1)  # Wait for 'a' with timeout
            except multiprocessing.TimeoutError:
                a = None
    
            try:
                # Lower timeout since we already waited with `a`
                b = result_b.get(timeout=0.2)  # Wait for 'b' with timeout
            except multiprocessing.TimeoutError:
                b = None
    
            assert a or b
            # Make sure a is always filled
            if b:
                b, a = b, a
    
            print(f'{a=}')
            print(f'{b=}')
    
            # make pyright happy
            assert a is not None
    
            if b:
                # make pyright happy
                assert b is not None
    
                assert not a.exception_class or not b.exception_class
>               assert issubclass(
                    a.exception_class or b.exception_class,  # type: ignore
                    portalocker.LockException,
                )
E               TypeError: issubclass() arg 1 must be a class

portalocker_tests/tests.py:383: TypeError
----------------------------- Captured stdout call -----------------------------
Locking /tmp/pytest-of-openhands/pytest-11/test_exclusive_processes_flock1/6461274342521995 False <built-in function flock>
a=LockResult(exception_class=None, exception_message=None, exception_repr=None)
b=LockResult(exception_class=None, exception_message=None, exception_repr=None)
_________________________ test_locker_mechanism[lockf] _________________________

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_locker_mechanism_lockf_0/17959903794014997' mode='r+' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING|EXCLUSIVE: 6>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
>           fcntl.flock(fd, flags)
E           BlockingIOError: [Errno 11] Resource temporarily unavailable

portalocker/portalocker.py:77: BlockingIOError

The above exception was the direct cause of the following exception:

self = <portalocker.utils.Lock object at 0x7f1b00bf6b90>, timeout = 0.1
check_interval = 0.25, fail_when_locked = False

    def acquire(self, timeout: typing.Optional[float]=None, check_interval: typing.Optional[float]=None, fail_when_locked: typing.Optional[bool]=None) -> typing.IO[typing.AnyStr]:
        """Acquire the locked filehandle"""
        timeout = coalesce(timeout, self.timeout)
        check_interval = coalesce(check_interval, self.check_interval)
        fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
    
        # Issue warning if timeout is used with blocking mode
        if timeout is not None and not self.flags & constants.LockFlags.NON_BLOCKING:
            warnings.warn('timeout has no effect in blocking mode', stacklevel=1)
    
        # If we already have a file handle, just return it
        if self.fh is not None:
            return self.fh
    
        # Try to acquire the lock
        start_time = time.time()
        while True:
            try:
                # Get a new file handle
                self.fh = self._get_fh()
    
                # Try to lock it
>               self._get_lock(self.fh)

portalocker/utils.py:189: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
portalocker/utils.py:247: in _get_lock
    portalocker.lock(fh, self.flags)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file = <_io.TextIOWrapper name='/tmp/pytest-of-openhands/pytest-11/test_locker_mechanism_lockf_0/17959903794014997' mode='r+' encoding='UTF-8'>
flags = <LockFlags.NON_BLOCKING|EXCLUSIVE: 6>

    def lock(file: typing.Union[int, HasFileno], flags: int) -> None:
        """Lock a file using POSIX flock."""
        if isinstance(file, int):
            fd = file
        else:
            fd = file.fileno()
    
        try:
            fcntl.flock(fd, flags)
        except OSError as e:
            if e.errno == errno.EAGAIN or e.errno == errno.EACCES:
>               raise exceptions.AlreadyLocked("File is already locked") from e
E               portalocker.exceptions.AlreadyLocked: File is already locked

portalocker/portalocker.py:80: AlreadyLocked

During handling of the above exception, another exception occurred:

tmpfile = '/tmp/pytest-of-openhands/pytest-11/test_locker_mechanism_lockf_0/17959903794014997'
locker = <built-in function lockf>

    @pytest.mark.skipif(
        os.name != 'posix',
        reason='Only posix systems have different lockf behaviour',
    )
    @pytest.mark.parametrize('locker', LOCKERS, indirect=True)
    def test_locker_mechanism(tmpfile, locker):
        '''Can we switch the locking mechanism?'''
        # We can test for flock vs lockf based on their different behaviour re.
        # locking the same file.
        with portalocker.Lock(tmpfile, 'a+', flags=LockFlags.EXCLUSIVE):
            # If we have lockf(), we cannot get another lock on the same file.
            if locker is fcntl.lockf:
>               portalocker.Lock(
                    tmpfile,
                    'r+',
                    flags=LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING,
                ).acquire(timeout=0.1)

portalocker_tests/tests.py:421: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <portalocker.utils.Lock object at 0x7f1b00bf6b90>, timeout = 0.1
check_interval = 0.25, fail_when_locked = False

    def acquire(self, timeout: typing.Optional[float]=None, check_interval: typing.Optional[float]=None, fail_when_locked: typing.Optional[bool]=None) -> typing.IO[typing.AnyStr]:
        """Acquire the locked filehandle"""
        timeout = coalesce(timeout, self.timeout)
        check_interval = coalesce(check_interval, self.check_interval)
        fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
    
        # Issue warning if timeout is used with blocking mode
        if timeout is not None and not self.flags & constants.LockFlags.NON_BLOCKING:
            warnings.warn('timeout has no effect in blocking mode', stacklevel=1)
    
        # If we already have a file handle, just return it
        if self.fh is not None:
            return self.fh
    
        # Try to acquire the lock
        start_time = time.time()
        while True:
            try:
                # Get a new file handle
                self.fh = self._get_fh()
    
                # Try to lock it
                self._get_lock(self.fh)
    
                # Prepare the file handle for use
                self._prepare_fh(self.fh)
    
                return self.fh
    
            except exceptions.AlreadyLocked:
                # File is already locked
                if fail_when_locked:
                    # If we should fail when locked, raise the exception
                    raise
    
                # Check if we've timed out
                if timeout is not None and time.time() - start_time > timeout:
>                   raise exceptions.LockException("Timeout waiting for lock")
E                   portalocker.exceptions.LockException: Timeout waiting for lock

portalocker/utils.py:204: LockException
=============================== warnings summary ===============================
../../home/openhands/.local/lib/python3.10/site-packages/_pytest/config/__init__.py:1428
  /home/openhands/.local/lib/python3.10/site-packages/_pytest/config/__init__.py:1428: PytestConfigWarning: Unknown config option: timeout
  
    self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")

portalocker_tests/tests.py::test_locker_mechanism[flock]
portalocker_tests/tests.py::test_locker_mechanism[lockf]
  /workspace/portalocker/portalocker/utils.py:175: UserWarning: timeout has no effect in blocking mode
    warnings.warn('timeout has no effect in blocking mode', stacklevel=1)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
--------------------------------- JSON report ----------------------------------
report saved to: report.json
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.10.12-final-0 _______________

Name                         Stmts   Miss Branch BrPart  Cover   Missing
------------------------------------------------------------------------
portalocker/__about__.py         6      0      0      0   100%
portalocker/__init__.py         18      0      0      0   100%
portalocker/__main__.py         69      0     20      0   100%
portalocker/constants.py        20      6      4      2    67%   20-24, 32
portalocker/exceptions.py       12      0      0      0   100%
portalocker/portalocker.py      60     35     22      3    39%   12-63, 86, 92-93, 97
portalocker/utils.py           173     29     40      4    82%   46, 75-104, 117, 124, 211->217, 214-215, 228-230, 290, 342, 370-372
------------------------------------------------------------------------
TOTAL                          358     70     86      9    78%
Coverage HTML written to dir htmlcov
FAIL Required test coverage of 100.0% not reached. Total coverage: 78.15%
=========================== short test summary info ============================
FAILED portalocker_tests/temporary_file_lock.py::test_temporary_file_lock - A...
FAILED portalocker_tests/test_combined.py::test_combined -   File "/tmp/pytes...
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[None-None]
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[None-0] - ...
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[None-0.001]
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0-None] - ...
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0-0] - Att...
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0-0.001]
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0.0005-None]
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0.0005-0]
FAILED portalocker_tests/test_semaphore.py::test_bounded_semaphore[0.0005-0.001]
FAILED portalocker_tests/tests.py::test_nonblocking[flock] - portalocker.exce...
FAILED portalocker_tests/tests.py::test_nonblocking[lockf] - portalocker.exce...
FAILED portalocker_tests/tests.py::test_exclusive_processes[flock-False] - Ty...
FAILED portalocker_tests/tests.py::test_locker_mechanism[lockf] - portalocker...
ERROR portalocker_tests/test_redis.py
ERROR portalocker_tests/test_redis.py
============= 15 failed, 25 passed, 3 warnings, 2 errors in 3.38s ==============