diff --git a/PKG-INFO b/PKG-INFO
index 8c1fa9c..720abb9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,228 +1,214 @@
 Metadata-Version: 2.1
 Name: portalocker
-Version: 2.2.1
+Version: 2.4.0
 Summary: Wraps the portalocker recipe for easy usage
 Home-page: https://github.com/WoLpH/portalocker
 Author: Rick van Hattem
 Author-email: wolph@wol.ph
 License: PSF
-Description: ############################################
-        portalocker - Cross-platform locking library
-        ############################################
-        
-        .. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
-            :alt: Linux Test Status
-            :target: https://travis-ci.com/WoLpH/portalocker
-        
-        .. 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.
-        
-        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.
-        
-        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 >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.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:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.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:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.LOCK_EX)
-        >>> 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>`_.
-        
-        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.
-        
-        
 Keywords: locking,locks,with statement,windows,linux,unix
 Platform: any
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.5
 Provides-Extra: docs
 Provides-Extra: tests
 Provides-Extra: redis
+License-File: LICENSE
+
+############################################
+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.
+
+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 >>fh, 'writing some stuff to my cache...'
+
+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.
+
+
+
diff --git a/README.rst b/README.rst
index 52a43e0..b07d74d 100644
--- a/README.rst
+++ b/README.rst
@@ -2,9 +2,9 @@
 portalocker - Cross-platform locking library
 ############################################
 
-.. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
+.. image:: https://github.com/WoLpH/portalocker/actions/workflows/python-package.yml/badge.svg?branch=master
     :alt: Linux Test Status
-    :target: https://travis-ci.com/WoLpH/portalocker
+    :target: https://github.com/WoLpH/portalocker/actions/
 
 .. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
     :alt: Windows Tests Status
@@ -47,6 +47,12 @@ 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:
 
 ::
@@ -117,39 +123,7 @@ To customize the opening and locking a manual approach is also possible:
 
 >>> import portalocker
 >>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.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:
-
->>> import portalocker
->>> with portalocker.Lock('somefile', timeout=1) as fh:
-...     print >>fh, 'writing some stuff to my cache...'
-
-To customize the opening and locking a manual approach is also possible:
-
->>> import portalocker
->>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.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:
-
->>> import portalocker
->>> with portalocker.Lock('somefile', timeout=1) as fh:
-...     print >>fh, 'writing some stuff to my cache...'
-
-To customize the opening and locking a manual approach is also possible:
-
->>> import portalocker
->>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.LOCK_EX)
+>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
 >>> file.seek(12)
 >>> file.write('foo')
 >>> file.close()
@@ -190,6 +164,13 @@ 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
 ---------
 
diff --git a/debian/changelog b/debian/changelog
index aa7481a..da93b6e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+portalocker (2.4.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 30 May 2022 23:11:52 -0000
+
 portalocker (2.2.1-1) unstable; urgency=medium
 
   * New upstream release (2.2.1)
diff --git a/portalocker.egg-info/PKG-INFO b/portalocker.egg-info/PKG-INFO
index 8c1fa9c..720abb9 100644
--- a/portalocker.egg-info/PKG-INFO
+++ b/portalocker.egg-info/PKG-INFO
@@ -1,228 +1,214 @@
 Metadata-Version: 2.1
 Name: portalocker
-Version: 2.2.1
+Version: 2.4.0
 Summary: Wraps the portalocker recipe for easy usage
 Home-page: https://github.com/WoLpH/portalocker
 Author: Rick van Hattem
 Author-email: wolph@wol.ph
 License: PSF
-Description: ############################################
-        portalocker - Cross-platform locking library
-        ############################################
-        
-        .. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
-            :alt: Linux Test Status
-            :target: https://travis-ci.com/WoLpH/portalocker
-        
-        .. 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.
-        
-        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.
-        
-        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 >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.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:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.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:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.LOCK_EX)
-        >>> 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>`_.
-        
-        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.
-        
-        
 Keywords: locking,locks,with statement,windows,linux,unix
 Platform: any
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.5
 Provides-Extra: docs
 Provides-Extra: tests
 Provides-Extra: redis
+License-File: LICENSE
+
+############################################
+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.
+
+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 >>fh, 'writing some stuff to my cache...'
+
+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.
+
+
+
diff --git a/portalocker.egg-info/SOURCES.txt b/portalocker.egg-info/SOURCES.txt
index db40da5..380671d 100644
--- a/portalocker.egg-info/SOURCES.txt
+++ b/portalocker.egg-info/SOURCES.txt
@@ -9,6 +9,7 @@ portalocker/__init__.py
 portalocker/constants.py
 portalocker/exceptions.py
 portalocker/portalocker.py
+portalocker/py.typed
 portalocker/redis.py
 portalocker/utils.py
 portalocker.egg-info/PKG-INFO
diff --git a/portalocker.egg-info/requires.txt b/portalocker.egg-info/requires.txt
index eb596f3..d405bd5 100644
--- a/portalocker.egg-info/requires.txt
+++ b/portalocker.egg-info/requires.txt
@@ -1,6 +1,6 @@
 
 [:platform_system == "Windows"]
-pywin32!=226
+pywin32>=226
 
 [docs]
 sphinx>=1.7.1
@@ -12,6 +12,5 @@ redis
 pytest>=5.4.1
 pytest-cov>=2.8.1
 sphinx>=3.0.3
-pytest-flake8>=1.0.5
 pytest-mypy>=0.8.0
 redis
diff --git a/portalocker/__about__.py b/portalocker/__about__.py
index 3bed460..ad60e5f 100644
--- a/portalocker/__about__.py
+++ b/portalocker/__about__.py
@@ -1,7 +1,7 @@
 __package_name__ = 'portalocker'
 __author__ = 'Rick van Hattem'
 __email__ = 'wolph@wol.ph'
-__version__ = '2.2.1'
+__version__ = '2.4.0'
 __description__ = '''Wraps the portalocker recipe for easy usage'''
 __url__ = 'https://github.com/WoLpH/portalocker'
 
diff --git a/portalocker/__init__.py b/portalocker/__init__.py
index b20b3d7..13699df 100644
--- a/portalocker/__init__.py
+++ b/portalocker/__init__.py
@@ -17,7 +17,7 @@ __author__ = __about__.__author__
 #: Current author's email address
 __email__ = __about__.__email__
 #: Version number
-__version__ = '2.2.1'
+__version__ = '2.4.0'
 #: Package description for Pypi
 __description__ = __about__.__description__
 #: Package homepage
diff --git a/portalocker/exceptions.py b/portalocker/exceptions.py
index 0a815b9..0a8594d 100644
--- a/portalocker/exceptions.py
+++ b/portalocker/exceptions.py
@@ -1,10 +1,18 @@
+import typing
+
+
 class BaseLockException(Exception):
     # Error codes:
     LOCK_FAILED = 1
 
-    def __init__(self, *args, fh=None, **kwargs):
+    def __init__(
+        self,
+        *args: typing.Any,
+        fh: typing.Optional[typing.IO] = None,
+        **kwargs: typing.Any,
+    ) -> None:
         self.fh = fh
-        Exception.__init__(self, *args, **kwargs)
+        Exception.__init__(self, *args)
 
 
 class LockException(BaseLockException):
diff --git a/portalocker/py.typed b/portalocker/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/portalocker/redis.py b/portalocker/redis.py
index b2468b9..08dbd4a 100644
--- a/portalocker/redis.py
+++ b/portalocker/redis.py
@@ -18,7 +18,7 @@ DEFAULT_UNAVAILABLE_TIMEOUT = 1
 DEFAULT_THREAD_SLEEP_TIME = 0.1
 
 
-class PubSubWorkerThread(client.PubSubWorkerThread):
+class PubSubWorkerThread(client.PubSubWorkerThread):  # type: ignore
 
     def run(self):
         try:
@@ -152,7 +152,7 @@ class RedisLock(utils.LockBase):
                         self.unavailable_timeout):  # pragma: no branch
                     continue
                 else:  # pragma: no cover
-                    subscribers = None
+                    subscribers = 0
 
             # Note: this should not be changed to an elif because the if
             # above can still end up here
diff --git a/portalocker/utils.py b/portalocker/utils.py
index 4226e9e..d7c94ca 100644
--- a/portalocker/utils.py
+++ b/portalocker/utils.py
@@ -1,17 +1,21 @@
 import abc
 import atexit
 import contextlib
+import logging
 import os
 import pathlib
 import random
 import tempfile
 import time
 import typing
+import warnings
 
 from . import constants
 from . import exceptions
 from . import portalocker
 
+logger = logging.getLogger(__name__)
+
 DEFAULT_TIMEOUT = 5
 DEFAULT_CHECK_INTERVAL = 0.25
 DEFAULT_FAIL_WHEN_LOCKED = False
@@ -25,7 +29,7 @@ __all__ = [
 Filename = typing.Union[str, pathlib.Path]
 
 
-def coalesce(*args, test_value=None):
+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.
@@ -53,7 +57,8 @@ def coalesce(*args, test_value=None):
 
 
 @contextlib.contextmanager
-def open_atomic(filename: Filename, binary: bool = True):
+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
@@ -126,21 +131,23 @@ class LockBase(abc.ABC):  # pragma: no cover
             fail_when_locked: bool = None):
         return NotImplemented
 
-    def _timeout_generator(self, timeout, check_interval):
-        timeout = coalesce(timeout, self.timeout, 0.0)
-        check_interval = coalesce(check_interval, self.check_interval, 0.0)
+    def _timeout_generator(self, timeout: typing.Optional[float],
+                           check_interval: typing.Optional[float]) \
+            -> typing.Iterator[int]:
+        f_timeout = coalesce(timeout, self.timeout, 0.0)
+        f_check_interval = coalesce(check_interval, self.check_interval, 0.0)
 
         yield 0
         i = 0
 
         start_time = time.perf_counter()
-        while start_time + timeout > time.perf_counter():
+        while start_time + f_timeout > time.perf_counter():
             i += 1
             yield i
 
             # Take low lock checks into account to stay within the interval
             since_start_time = time.perf_counter() - start_time
-            time.sleep(max(0.001, (i * check_interval) - since_start_time))
+            time.sleep(max(0.001, (i * f_check_interval) - since_start_time))
 
     @abc.abstractmethod
     def release(self):
@@ -149,15 +156,20 @@ class LockBase(abc.ABC):  # pragma: no cover
     def __enter__(self):
         return self.acquire()
 
-    def __exit__(self, type_, value, tb):
+    def __exit__(self,
+                 exc_type: typing.Optional[typing.Type[BaseException]],
+                 exc_value: typing.Optional[BaseException],
+                 traceback: typing.Any,  # Should be typing.TracebackType
+                 ) -> typing.Optional[bool]:
         self.release()
+        return None
 
     def __delete__(self, instance):
         instance.release()
 
 
 class Lock(LockBase):
-    '''Lock manager with build-in timeout
+    '''Lock manager with built-in timeout
 
     Args:
         filename: filename
@@ -182,7 +194,7 @@ class Lock(LockBase):
             self,
             filename: Filename,
             mode: str = 'a',
-            timeout: float = DEFAULT_TIMEOUT,
+            timeout: float = None,
             check_interval: float = DEFAULT_CHECK_INTERVAL,
             fail_when_locked: bool = DEFAULT_FAIL_WHEN_LOCKED,
             flags: constants.LockFlags = LOCK_METHOD, **file_open_kwargs):
@@ -192,6 +204,11 @@ class Lock(LockBase):
         else:
             truncate = False
 
+        if timeout is None:
+            timeout = DEFAULT_TIMEOUT
+        elif not (flags & constants.LockFlags.NON_BLOCKING):
+            warnings.warn('timeout has no effect in blocking mode')
+
         self.fh: typing.Optional[typing.IO] = None
         self.filename: str = str(filename)
         self.mode: str = mode
@@ -209,6 +226,10 @@ class Lock(LockBase):
 
         fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
 
+        if not (self.flags & constants.LockFlags.NON_BLOCKING) \
+                and timeout is not None:
+            warnings.warn('timeout has no effect in blocking mode')
+
         # If we already have a filehandle, return it
         fh = self.fh
         if fh:
@@ -397,10 +418,9 @@ class BoundedSemaphore(LockBase):
         assert not self.lock, 'Already locked'
 
         filenames = self.get_filenames()
-        print('filenames', filenames)
 
-        for _ in self._timeout_generator(timeout, check_interval):  # pragma:
-            print('trying lock', filenames)
+        for n in self._timeout_generator(timeout, check_interval):  # pragma:
+            logger.debug('trying lock (attempt %d) %r', n, filenames)
             # no branch
             if self.try_lock(filenames):  # pragma: no branch
                 return self.lock  # pragma: no cover
@@ -410,11 +430,11 @@ class BoundedSemaphore(LockBase):
     def try_lock(self, filenames: typing.Sequence[Filename]) -> bool:
         filename: Filename
         for filename in filenames:
-            print('trying lock for', filename)
+            logger.debug('trying lock for %r', filename)
             self.lock = Lock(filename, fail_when_locked=True)
             try:
                 self.lock.acquire()
-                print('locked', filename)
+                logger.debug('locked %r', filename)
                 return True
             except exceptions.AlreadyLocked:
                 pass
diff --git a/portalocker_tests/tests.py b/portalocker_tests/tests.py
index 3cc9758..514b028 100644
--- a/portalocker_tests/tests.py
+++ b/portalocker_tests/tests.py
@@ -206,3 +206,14 @@ def test_shared(tmpfile):
     portalocker.unlock(f)
     f.close()
 
+
+def test_blocking_timeout(tmpfile):
+    flags = portalocker.LockFlags.SHARED
+
+    with pytest.warns(UserWarning):
+        with portalocker.Lock(tmpfile, timeout=5, flags=flags):
+            pass
+
+    lock = portalocker.Lock(tmpfile, flags=flags)
+    with pytest.warns(UserWarning):
+        lock.acquire(timeout=5)
diff --git a/setup.cfg b/setup.cfg
index 6281d19..e50242f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,13 +1,5 @@
 [metadata]
-description-file = README.rst
-
-[build_sphinx]
-source-dir = docs/
-build-dir = docs/_build
-all_files = 1
-
-[upload_sphinx]
-upload-dir = docs/_build/html
+description_file = README.rst
 
 [bdist_wheel]
 universal = 1
diff --git a/setup.py b/setup.py
index 4365703..974e0aa 100644
--- a/setup.py
+++ b/setup.py
@@ -4,19 +4,11 @@ import os
 import re
 import sys
 import typing
-from distutils.version import LooseVersion
 
 import setuptools
 from setuptools import __version__ as setuptools_version
 from setuptools.command.test import test as TestCommand
 
-if LooseVersion(setuptools_version) < LooseVersion('38.3.0'):
-    raise SystemExit(
-        'Your `setuptools` version is old. '
-        'Please upgrade setuptools by running `pip install -U setuptools` '
-        'and try again.'
-    )
-
 # To prevent importing about and thereby breaking the coverage info we use this
 # exec hack
 about: typing.Dict[str, str] = {}
@@ -27,14 +19,13 @@ tests_require = [
     'pytest>=5.4.1',
     'pytest-cov>=2.8.1',
     'sphinx>=3.0.3',
-    'pytest-flake8>=1.0.5',
     'pytest-mypy>=0.8.0',
     'redis',
 ]
 
 
 class PyTest(TestCommand):
-    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
+    user_options = [('pytest-args=', 'a', 'Arguments to pass to pytest')]
 
     def initialize_options(self):
         TestCommand.initialize_options(self)
@@ -113,19 +104,22 @@ if __name__ == '__main__':
         classifiers=[
             'Intended Audience :: Developers',
             'Programming Language :: Python',
-            'Programming Language :: Python :: 2.7',
-            'Programming Language :: Python :: 3.3',
-            'Programming Language :: Python :: 3.4',
             'Programming Language :: Python :: 3.5',
             'Programming Language :: Python :: 3.6',
+            'Programming Language :: Python :: 3.7',
+            'Programming Language :: Python :: 3.8',
+            'Programming Language :: Python :: 3.9',
+            'Programming Language :: Python :: 3.10',
             'Programming Language :: Python :: Implementation :: CPython',
             'Programming Language :: Python :: Implementation :: PyPy',
         ],
+        python_requires='>=3.5',
         keywords='locking, locks, with statement, windows, linux, unix',
         author=about['__author__'],
         author_email=about['__email__'],
         url=about['__url__'],
         license='PSF',
+        package_data=dict(portalocker=['py.typed', 'msvcrt.pyi']),
         packages=setuptools.find_packages(exclude=[
             'examples', 'portalocker_tests']),
         # zip_safe=False,
@@ -135,7 +129,8 @@ if __name__ == '__main__':
             'test': PyTest,
         },
         install_requires=[
-            'pywin32!=226; platform_system == "Windows"',
+            # Due to CVE-2021-32559 updating the pywin32 requirement
+            'pywin32>=226; platform_system == "Windows"',
         ],
         tests_require=tests_require,
         extras_require=dict(