New Upstream Release - python-fasteners

Ready changes

Summary

Merged new upstream version: 0.18 (was: 0.17.3).

Resulting package

Built on 2023-01-31T17:26 (took 4m3s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-fasteners

Lintian Result

Diff

diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml
new file mode 100644
index 0000000..c697c89
--- /dev/null
+++ b/.github/workflows/build_docs.yml
@@ -0,0 +1,51 @@
+name: GitHub Pages
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+
+jobs:
+  deploy:
+    runs-on: ubuntu-20.04
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Setup Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: '3.8'
+
+      - name: Upgrade pip
+        run: |
+          # install pip=>20.1 to use "pip cache dir"
+          python3 -m pip install --upgrade pip
+
+      - name: Get pip cache dir
+        id: pip-cache
+        run: echo "::set-output name=dir::$(pip cache dir)"
+
+      - name: Cache dependencies
+        uses: actions/cache@v2
+        with:
+          path: ${{ steps.pip-cache.outputs.dir }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-pip-
+
+      - name: Install dependencies
+        run: python3 -m pip install -r ./requirements-docs.txt
+
+      - name: Install itself
+        run: python3 -m pip install -e .
+
+      - run: mkdocs build
+
+      - name: Deploy
+        uses: peaceiris/actions-gh-pages@v3
+        if: ${{ github.ref == 'refs/heads/main' }}
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: ./site
\ No newline at end of file
diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
index f498d48..85766b0 100644
--- a/.github/workflows/run_tests.yml
+++ b/.github/workflows/run_tests.yml
@@ -5,9 +5,9 @@ name: run tests
 
 on:
   push:
-    branches: [ master ]
+    branches: [ main ]
   pull_request:
-    branches: [ master ]
+    branches: [ main ]
 
 jobs:
   build:
@@ -16,15 +16,12 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.6, 3.7, 3.8, 3.9, '3.10', pypy3]
-        exclude:
-          - os: macos-latest
-            python-version: pypy3
+        python-version: [3.7, 3.8, 3.9, '3.10', 'pypy3.7', 'pypy3.9']
 
     steps:
     - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
@@ -34,4 +31,5 @@ jobs:
         python -m pip install -r requirements-test.txt
     - name: Test with pytest
       run: |
-        pytest
+        pytest tests/
+        pytest tests_eventlet/
diff --git a/.gitignore b/.gitignore
index ca1e961..3895d47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 *.egg-info
 __pycache__
-dist
-build
+dist/
+build/
+site/
+venv/
diff --git a/CHANGELOG b/CHANGELOG
deleted file mode 100644
index 2207e9c..0000000
--- a/CHANGELOG
+++ /dev/null
@@ -1,120 +0,0 @@
-0.17.3:
-  - Allow writer to become a reader in thread ReaderWriter lock
-
-0.17.2:
-  - Remove unnecessary setuptools pin
-
-0.17.1:
-  - Switch to the modern python package build infrastructure
-
-0.17: [NEVER RELEASED]
-  - Remove support for python 3.5 and earlier, including 2.7
-  - Add support for python 3.9 and 3.10
-  - Fix a conflict with django lock
-  - Add __version__ and __all__ attributes
-  - Fix a failure to parse README as utf-8
-  - Move from nosetest to pytest and cleanup testing infrastructure
-
-0.16.3:
-  - Fix a failure to parse README as utf-8 on python2
-
-0.16.2:
-  - Fix a failure to parse README as utf-8
-
-0.16.1: [YANKED]
-
-0.16:
-  - Move from travis and appveyor to github actions
-  - Add interprocess reader writer lock
-  - Improve README
-  - remove unused eventlet import
-  - use stdlib monotonic instead of external for python >= 3.4
-
-0.15:
-  - Add testing for additional python versions
-  - Remove python 2.6 support
-  - Remove eventlet dependency and use
-    threading.current_thread instead
-
-0.14:
-  - Allow providing a custom exception logger
-    to 'locked' decorator
-  - Allow providing a custom logger to process
-    lock class
-  - Fix issue #12
-
-0.13:
-  - Fix 'ensure_tree' check on freebsd
-
-0.12:
-  - Use a tiny retry util helper class
-    for performing process locking
-    retries.
-
-0.11:
-  - Directly use monotonic.monotonic.
-  - Use BLATHER level for previously
-    INFO/DEBUG statements.
-
-0.10:
-  - Add LICENSE in generated source tarballs
-  - Add a version.py file that can be used to
-    extract the current version.
-
-0.9:
-  - Allow providing a non-standard (eventlet or
-    other condition class) to the r/w lock for
-    cases where it is useful to do so.
-  - Instead of having the r/w lock take a find
-    eventlet keyword argument, allow for it to
-    be provided a function that will be later
-    called to get the current thread. This allows
-    for the current *hack* to be easily removed
-    by users (if they so desire).
-
-0.8:
-  - Add fastener logo (from openclipart).
-  - Ensure r/w writer -> reader -> writer
-    lock acquisition.
-  - Attempt to use the monotonic pypi module
-    if its installed for monotonically increasing
-    time on python versions where this is not
-    built-in.
-
-0.7:
-  - Add helpful `locked` decorator that can
-    lock a method using a found attribute (a lock
-    object or list of lock objects) in the
-    instance the method is attached to.
-  - Expose top level `try_lock` function.
-
-0.6:
-  - Allow the sleep function to be provided (so that
-    various alternatives other than time.sleep can
-    be used), ie eventlet.sleep (or other).
-  - Remove dependency on oslo.utils (replace with
-    small utility code that achieves the same effect).
-
-0.5:
-  - Make it possible to provide a acquisition timeout
-    to the interprocess lock (which when acquisition
-    can not complete in the desired time will return
-    false).
-
-0.4:
-  - Have the interprocess lock acquire take a blocking
-    keyword argument (defaulting to true) that can avoid
-    blocking trying to acquire the lock
-
-0.3:
-  - Renamed from 'shared_lock' to 'fasteners'
-
-0.2.1
-  - Fix delay not working as expected
-
-0.2:
-  - Add a interprocess lock
-
-0.1:
-  - Add travis yaml file
-  - Initial commit/import
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..64641c4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,117 @@
+# ChangeLog
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+
+## [Unreleased]
+
+## [0.18]
+  - Reshuffle the process lock code and properly document it.
+  - Revamp the docs and switch from sphinx to mkdocs
+  - Remove difficult to use tread lock features from docs
+  - Bring back support for eventlet `spawn_n`
+  - Remove support for python3.6. It should still work, but is no longer tested.
+
+## [0.17.3]:
+  - Allow writer to become a reader in thread ReaderWriter lock
+
+## [0.17.2]:
+  - Remove unnecessary setuptools pin
+
+## [0.17.1]:
+  - Switch to the modern python package build infrastructure
+
+## [0.17]: [NEVER RELEASED]
+  - Remove support for python 3.5 and earlier, including 2.7
+  - Add support for python 3.9 and 3.10
+  - Fix a conflict with django lock
+  - Add `__version__` and `__all__` attributes
+  - Fix a failure to parse README as utf-8
+  - Move from nosetest to pytest and cleanup testing infrastructure
+
+## [0.16.3]:
+  - Fix a failure to parse README as utf-8 on python2
+
+## [0.16.2]:
+  - Fix a failure to parse README as utf-8
+
+## [0.16.1]: [YANKED]
+
+## [0.16]:
+  - Move from travis and appveyor to github actions
+  - Add interprocess reader writer lock
+  - Improve README
+  - remove unused eventlet import
+  - use stdlib monotonic instead of external for python >= 3.4
+
+## [0.15]:
+  - Add testing for additional python versions
+  - Remove python 2.6 support
+  - Remove eventlet dependency and use
+    threading.current_thread instead
+
+## [0.14]:
+  - Allow providing a custom exception logger to 'locked' decorator
+  - Allow providing a custom logger to process lock class
+  - Fix issue #12
+
+## [0.13]:
+  - Fix 'ensure_tree' check on freebsd
+
+## [0.12]:
+  - Use a tiny retry util helper class for performing process locking retries.
+
+## [0.11]:
+  - Directly use monotonic.monotonic.
+  - Use BLATHER level for previously INFO/DEBUG statements.
+
+## [0.10]:
+  - Add LICENSE in generated source tarballs
+  - Add a version.py file that can be used to extract the current version.
+
+## [0.9]:
+  - Allow providing a non-standard (eventlet or other condition class) to the 
+    r/w lock for cases where it is useful to do so.
+  - Instead of having the r/w lock take a find eventlet keyword argument, allow 
+    for it to be provided a function that will be later called to get the 
+    current thread. This allows for the current *hack* to be easily removed
+    by users (if they so desire).
+
+## [0.8]:
+  - Add fastener logo (from openclipart).
+  - Ensure r/w writer -> reader -> writer lock acquisition.
+  - Attempt to use the monotonic pypi module if its installed for monotonically 
+    increasing time on python versions where this is not built-in.
+
+## [0.7]:
+  - Add helpful `locked` decorator that can lock a method using a found 
+    attribute (a lock object or list of lock objects) in the instance the method 
+    is attached to.
+  - Expose top level `try_lock` function.
+
+## [0.6]:
+  - Allow the sleep function to be provided (so that various alternatives other 
+    than time.sleep can be used), ie eventlet.sleep (or other).
+  - Remove dependency on oslo.utils (replace with small utility code that 
+    achieves the same effect).
+
+## [0.5]:
+  - Make it possible to provide an acquisition timeout to the interprocess lock 
+    (which when acquisition can not complete in the desired time will return
+    false).
+
+## [0.4]:
+  - Have the interprocess lock acquire take a blocking keyword argument 
+    (defaulting to true) that can avoid blocking trying to acquire the lock
+
+## [0.3]:
+  - Renamed from 'shared_lock' to 'fasteners'
+
+## [0.2.1]
+  - Fix delay not working as expected
+
+## [0.2]:
+  - Add a interprocess lock
+
+## [0.1]:
+  - Add travis yaml file
+  - Initial commit/import
diff --git a/MANIFEST.in b/MANIFEST.in
index f7aec42..fc49293 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1 @@
-include CHANGELOG
+include CHANGELOG.md
diff --git a/README.md b/README.md
index 65e5023..3504c65 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Fasteners
 [![Downloads](https://img.shields.io/pypi/dm/fasteners.svg)](https://pypi.python.org/pypi/fasteners/)
 [![Latest version](https://img.shields.io/pypi/v/fasteners.svg)](https://pypi.python.org/pypi/fasteners/)
 
-Cross platform locks for threads and processes.
+Cross-platform locks for threads and processes.
 
 🔩 Install
 ----------
@@ -67,6 +67,8 @@ rw_lock.release_write_lock()
 Python standard library provides a lock for threads (both a reentrant one, and a
 non-reentrant one, see below). Fasteners extends this, and provides a lock for
 processes, as well as Reader Writer locks for both threads and processes.
+Definitions of terms used in this overview can be found in the
+[glossary](https://fasteners.readthedocs.io/en/latest/guide/glossary/).
 
 The specifics of the locks are as follows:
 
@@ -96,7 +98,7 @@ features are as follows:
 
 ### Thread locks
 
-Fasteners do not provide a simple thread lock, but for the sake of comparison note that the `threading` module
+Fasteners does not provide a simple thread lock, but for the sake of comparison note that the `threading` module
 provides both a reentrant and non-reentrant locks:
 
 | lock | reentrant | mandatory |
@@ -109,42 +111,8 @@ The `fasteners.ReaderWriterLock` at the moment is as follows:
 
 | lock | reentrant | mandatory | upgradable | preference | 
 |------|-----------|-----------|-------------|------------|
-| fasteners.ReaderWriterLock | ✔ | ✘ | ✘ | reader |
-
-🔩 Glossary
------------
-To learn more about the various aspects of locks, check the wikipedia pages for 
-[locks](https://en.wikipedia.org/wiki/Lock_(computer_science)) and 
-[readers writer locks](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) as well as the
-[resources](https://github.com/harlowja/fasteners/blob/master/doc/source/api/process_lock.rst) listed in the 
-documentation. Here we briefly mention the main notions used above.
-
-* **Lock** - a mechanism that prevents two or more threads or processes from running the same code at the same time.
-* **Readers writer lock** - a mechanism that prevents two or more threads from having write (or write and read) access, 
-while allowing multiple readers.
-* **Reentrant lock** - a lock that can be acquired (and then released) multiple times, as in:
-
-```python
-with lock:
-    with lock:
-        ... # some code
-```
-* **Mandatory lock** (as opposed to advisory lock) - a lock that is enforced by the operating system, rather than
-by the cooperation between threads or processes
-* **Upgradable readers writer lock** - a readers writer lock that can be upgraded from reader to writer (or downgraded
-from writer to reader) without losing the lock that is already held, as in:
-```python
-with rw_lock.read_lock():
-    ... # read access
-    with rw_lock.write_lock():
-        ... # write access
-    ... # read access
-```
-* **Readers writer lock preference** - describes the behaviour when multiple threads or processes are waiting for 
-access. Some of the patterns are:
-    * **Reader preference** - If lock is held by readers, then new readers will get immediate access. This can result
-    in writers waiting forever (writer starvation)
-    * **Writer preference** - If writer is waiting for a lock, then all the new readers (and writers) will be queued
-    after the writer.
-    * **Phase fair** - Lock that alternates between readers and writers.
+| fasteners.ReaderWriterLock | ✔ | ✘ | ✘ | writer |
 
+If your threads are created by some other means than the standard library `threading`
+module (for example `eventlet`), you may need to provide the corresponding thread
+identification and synchronisation functions to the `ReaderWriterLock`.
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 2df0601..0113eec 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-fasteners (0.18-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 31 Jan 2023 17:23:13 -0000
+
 python-fasteners (0.17.3-2) unstable; urgency=medium
 
   * Uploading to unstable.
diff --git a/doc/source/api/lock.rst b/doc/source/api/lock.rst
deleted file mode 100644
index 0d625fe..0000000
--- a/doc/source/api/lock.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-=======
- Lock
-=======
-
--------
-Classes
--------
-
-.. autoclass:: fasteners.lock.ReaderWriterLock
-   :members:
-
-----------
-Decorators
-----------
-
-.. autofunction:: fasteners.lock.read_locked
-
-.. autofunction:: fasteners.lock.write_locked
-
-.. autofunction:: fasteners.lock.locked
-
-----------------
-Helper functions
-----------------
-
-.. autofunction:: fasteners.lock.try_lock
diff --git a/doc/source/api/process_lock.rst b/doc/source/api/process_lock.rst
deleted file mode 100644
index f2b4c93..0000000
--- a/doc/source/api/process_lock.rst
+++ /dev/null
@@ -1,83 +0,0 @@
-==============
- Process lock
-==============
-
-Fasteners inter-process locks are cross-platform and are released automatically
-if the process crashes. They are based on the platform specific locking
-mechanisms:
-
-* fcntl for posix (Linux and OSX)
-* LockFileEx (via pywin32) and \_locking (via msvcrt) for Windows
-
-The intersection of fcntl and LockFileEx features is quite small, hence you
-should always assume that:
-
-* Locks are advisory. They do not prevent the modification of the locked file
-  by other processes.
-
-* Locks can be unintentionally released by simply opening and closing the file
-  descriptor, so lock files must be accessed only using provided abstractions.
-
-* Locks are not reentrant_. An attempt to acquire a lock multiple times can
-  result in a deadlock or a crash upon a release of the lock.
-
-* Reader writer locks are not upgradeable_. An attempt to get a reader's lock
-  while holding a writer's lock (or vice versa) can result in a deadlock or a
-  crash upon a release of the lock.
-
-* There are no guarantees regarding usage by multiple threads in a
-  single process. The locks work only between processes.
-
-To learn more about the complications of locking on different platforms we
-recommend the following resources:
-
-* `File locking in Linux (blog post)`_
-
-* `On the Brokenness of File Locking (blog post)`_
-
-* `Everything you never wanted to know about file locking (blog post)`_
-
-* `Record Locking (course notes)`_
-
-* `Windows NT Files -- Locking (pywin32 docs)`_
-
-* `_locking (Windows Dev Center)`_
-
-* `LockFileEx function (Windows Dev Center)`_
-
-.. _upgradeable: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Upgradable_RW_lock>
-.. _reentrant: https://en.wikipedia.org/wiki/Reentrant_mutex
-.. _File locking in Linux (blog post): https://gavv.github.io/articles/file-locks/
-.. _On the Brokenness of File Locking (blog post): http://0pointer.de/blog/projects/locking.html
-.. _Record Locking (course notes): http://poincare.matf.bg.ac.rs/~ivana/courses/ps/sistemi_knjige/pomocno/apue/APUE/0201433079/ch14lev1sec3.html
-.. _Windows NT Files -- Locking (pywin32 docs): http://timgolden.me.uk/pywin32-docs/Windows_NT_Files_.2d.2d_Locking.html
-.. _\_locking (Windows Dev Center): https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/locking?view=vs-2019
-.. _LockFileEx function (Windows Dev Center): https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
-.. _Everything you never wanted to know about file locking (blog post): https://chris.improbable.org/2010/12/16/everything-you-never-wanted-to-know-about-file-locking/
-
--------
-Classes
--------
-
-.. autoclass:: fasteners.process_lock.InterProcessLock
-   :members:
-
-.. autoclass:: fasteners.process_lock._InterProcessLock
-   :members:
-
-.. autoclass:: fasteners.process_lock.InterProcessReaderWriterLock
-   :members:
-
-.. autoclass:: fasteners.process_lock._InterProcessReaderWriterLock
-   :members:
-
-
-----------
-Decorators
-----------
-
-.. autofunction:: fasteners.process_lock.interprocess_locked
-
-.. autofunction:: fasteners.process_lock.interprocess_read_locked
-
-.. autofunction:: fasteners.process_lock.interprocess_write_locked
diff --git a/doc/source/conf.py b/doc/source/conf.py
deleted file mode 100644
index dd20a54..0000000
--- a/doc/source/conf.py
+++ /dev/null
@@ -1,291 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Fasteners documentation build configuration file, created by
-# sphinx-quickstart on Fri Jun  5 14:47:29 2015.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys
-import os
-import shlex
-
-from fasteners import version as fasteners_version
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
-    'sphinx.ext.doctest',
-    'sphinx.ext.autodoc',
-    'sphinx.ext.viewcode',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Fasteners'
-copyright = u'2015, OpenStack Foundation, Yahoo!'
-author = u'Joshua Harlow, OpenStack Developers'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = fasteners_version.version_string()
-
-# The full version, including alpha/beta/rc tags.
-release = fasteners_version.version_string()
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = 'alabaster'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = "img/safety-pin-small.png"
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# Add any extra paths that contain custom files (such as robots.txt or
-# .htaccess) here, relative to this directory. These files are copied
-# directly to the root of the documentation.
-#html_extra_path = []
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Language to be used for generating the HTML full-text search index.
-# Sphinx supports the following languages:
-#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
-#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
-#html_search_language = 'en'
-
-# A dictionary with options for the search language support, empty by default.
-# Now only 'ja' uses this config value
-#html_search_options = {'type': 'default'}
-
-# The name of a javascript file (relative to the configuration directory) that
-# implements a search results scorer. If empty, the default will be used.
-#html_search_scorer = 'scorer.js'
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Fastenersdoc'
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-
-# Latex figure (float) alignment
-#'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-  (master_doc, 'Fasteners.tex', u'Fasteners Documentation',
-   u'Joshua Harlow', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'fasteners', u'Fasteners Documentation',
-     [author], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-  (master_doc, 'Fasteners', u'Fasteners Documentation',
-   author, 'Fasteners', 'One line description of project.',
-   'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
diff --git a/doc/source/examples.rst b/doc/source/examples.rst
deleted file mode 100644
index bad1cc7..0000000
--- a/doc/source/examples.rst
+++ /dev/null
@@ -1,250 +0,0 @@
-Examples
-========
-
--------------------
-Inter-process locks
--------------------
-
-.. note::
-
-  Launch multiple of these at the same time to see the lock(s) in action.
-
-.. warning::
-
-  There are no guarantees regarding usage by multiple threads in a
-  single process with these locks (you will have to ensure single process
-  safety yourself using traditional thread based locks). In other words this
-  lock works **only** between processes.
-
-Lock API
---------
-
-Using a decorator:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    @fasteners.interprocess_locked('/tmp/tmp_lock_file')
-    def test():
-        for i in range(10):
-            print('I have the lock')
-            time.sleep(1)
-
-    print('Waiting for the lock')
-    test()
-
-Using a context manager:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    def test():
-        for i in range(10):
-            with fasteners.InterProcessLock('/tmp/tmp_lock_file'):
-                print('I have the lock')
-                time.sleep(1)
-
-    test()
-
-Acquiring and releasing manually:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    def test():
-        a_lock = fasteners.InterProcessLock('/tmp/tmp_lock_file')
-        for i in range(10):
-            gotten = a_lock.acquire(blocking=False)
-            try:
-                if gotten:
-                    print('I have the lock')
-                    time.sleep(0.2)
-                else:
-                    print('I do not have the lock')
-                    time.sleep(0.1)
-            finally:
-                if gotten:
-                    a_lock.release()
-
-    test()
-
-
-Reader Writer Lock API
-----------------------
-
-Reader lock using a decorator:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    @fasteners.interprocess_read_locked('/tmp/tmp_lock_file')
-    def test():
-        for i in range(10):
-            print('I have the readers lock')
-            time.sleep(1)
-
-    print('Waiting for the lock')
-    test()
-
-
-Writer lock using a context manager:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    def test():
-        for i in range(10):
-            with fasteners.InterProcessReaderWriterLock('/tmp/tmp_lock_file').write_lock():
-                print('I have the writers lock')
-                time.sleep(1)
-
-    test()
-
-
-Acquiring and releasing manually:
-
-.. code-block:: python
-
-    import time
-
-    import fasteners
-
-    def test():
-        a_lock = fasteners.InterProcessReaderWriterLock('/tmp/tmp_lock_file')
-        for i in range(10):
-            gotten = a_lock.acquire_read_lock(blocking=False)
-            try:
-                if gotten:
-                    print('I have the readers lock')
-                    time.sleep(0.2)
-                else:
-                    print('I do not have the readers lock')
-                    time.sleep(0.1)
-            finally:
-                if gotten:
-                    a_lock.release_read_lock()
-
-    test()
-
-
--------------------
-Inter-thread locks
--------------------
-
-Lock API
---------
-
-Using a decorator:
-
-.. code-block:: python
-
-    import threading
-
-    import fasteners
-
-    class NotThreadSafeThing(object):
-        def __init__(self):
-            self._lock = threading.Lock()
-
-        @fasteners.locked
-        def do_something(self):
-            print("Doing something in a thread safe manner")
-
-    o = NotThreadSafeThing()
-    o.do_something()
-
-
-Multiple locks using a single decorator:
-
-.. code-block:: python
-
-    import threading
-
-    import fasteners
-
-    class NotThreadSafeThing(object):
-        def __init__(self):
-            self._locks = [threading.Lock(), threading.Lock()]
-
-        @fasteners.locked(lock='_locks')
-        def do_something(self):
-            print("Doing something in a thread safe manner")
-
-    o = NotThreadSafeThing()
-    o.do_something()
-
-
-Manual lock without blocking:
-
-.. code-block:: python
-
-    import threading
-
-    import fasteners
-
-    t = threading.Lock()
-    with fasteners.try_lock(t) as gotten:
-        if gotten:
-            print("I got the lock")
-        else:
-            print("I did not get the lock")
-
-
-
-Reader Writer lock API
-----------------------
-
-Using a context manager:
-
-.. code-block:: python
-
-    import random
-    import threading
-    import time
-
-    import fasteners
-
-    def read_something(ident, rw_lock):
-        with rw_lock.read_lock():
-            print("Thread %s is reading something" % ident)
-            time.sleep(1)
-
-    def write_something(ident, rw_lock):
-        with rw_lock.write_lock():
-            print("Thread %s is writing something" % ident)
-            time.sleep(2)
-
-    rw_lock = fasteners.ReaderWriterLock()
-    threads = []
-    for i in range(0, 10):
-        is_writer = random.choice([True, False])
-        if is_writer:
-            threads.append(threading.Thread(target=write_something,
-                                            args=(i, rw_lock)))
-        else:
-            threads.append(threading.Thread(target=read_something,
-                                            args=(i, rw_lock)))
-
-    try:
-        for t in threads:
-            t.start()
-    finally:
-        while threads:
-            t = threads.pop()
-            t.join()
-
diff --git a/doc/source/img/safety-pin-small.png b/doc/source/img/safety-pin-small.png
deleted file mode 100644
index a882be5..0000000
Binary files a/doc/source/img/safety-pin-small.png and /dev/null differ
diff --git a/doc/source/index.rst b/doc/source/index.rst
deleted file mode 100644
index 1f501f8..0000000
--- a/doc/source/index.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-Welcome to Fasteners's documentation!
-=====================================
-
-A python `package`_ that provides useful locks.
-
-*Contents:*
-
-.. toctree::
-   :maxdepth: 3
-
-   api/lock
-   api/process_lock
-
-   examples
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
-.. _package: https://pypi.python.org/pypi/fasteners
-
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
new file mode 120000
index 0000000..04c99a5
--- /dev/null
+++ b/docs/CHANGELOG.md
@@ -0,0 +1 @@
+../CHANGELOG.md
\ No newline at end of file
diff --git a/docs/api/inter_process.md b/docs/api/inter_process.md
new file mode 100644
index 0000000..f926144
--- /dev/null
+++ b/docs/api/inter_process.md
@@ -0,0 +1,19 @@
+# Process Lock API
+
+::: fasteners.process_lock.InterProcessLock
+
+::: fasteners.process_lock.InterProcessReaderWriterLock
+
+## Decorators
+
+::: fasteners.process_lock.interprocess_locked
+    rendering:
+        heading_level: 3
+
+::: fasteners.process_lock.interprocess_read_locked
+    rendering:
+        heading_level: 3
+
+::: fasteners.process_lock.interprocess_write_locked
+    rendering:
+        heading_level: 3
\ No newline at end of file
diff --git a/docs/api/inter_thread.md b/docs/api/inter_thread.md
new file mode 100644
index 0000000..0f251ea
--- /dev/null
+++ b/docs/api/inter_thread.md
@@ -0,0 +1,3 @@
+# Thread lock API
+
+::: fasteners.lock.ReaderWriterLock
diff --git a/docs/guide/glossary.md b/docs/guide/glossary.md
new file mode 100644
index 0000000..f35dc1d
--- /dev/null
+++ b/docs/guide/glossary.md
@@ -0,0 +1,37 @@
+# Glossary
+
+To learn more about the various aspects of locks, check the wikipedia pages for
+[locks](https://en.wikipedia.org/wiki/Lock_(computer_science)) and
+[readers writer locks](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) 
+Here we briefly mention the main notions used in the documentation.
+
+* **Lock** - a mechanism that prevents two or more threads or processes from running the same code at the same time.
+* **Readers writer lock** - a mechanism that prevents two or more threads from having write (or write and read) access,
+  while allowing multiple readers.
+* **Reentrant lock** - a lock that can be acquired (and then released) multiple times, as in:
+ 
+    ```python
+    with lock:
+        with lock:
+            ... # some code
+    ```
+  
+* **Mandatory lock** (as opposed to advisory lock) - a lock that is enforced by the operating system, rather than
+  by the cooperation between threads or processes
+* **Upgradable readers writer lock** - a readers writer lock that can be upgraded from reader to writer (or downgraded
+  from writer to reader) without losing the lock that is already held, as in:
+```python
+with rw_lock.read_lock():
+    ... # read access
+    with rw_lock.write_lock():
+        ... # write access
+    ... # read access
+```
+* **Readers writer lock preference** - describes the behaviour when multiple threads or processes are waiting for
+  access. Some of the patterns are:
+    * **Reader preference** - If lock is held by readers, then new readers will get immediate access. This can result
+      in writers waiting forever (writer starvation).
+    * **Writer preference** - If writer is waiting for a lock, then all the new readers (and writers) will be queued
+      after it. This can result in readers waiting forever (reader starvation).
+    * **Phase fair** - Lock that alternates between readers and writers.
+
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 0000000..ec09763
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,57 @@
+# User Guide
+
+## Basic Exclusive Lock Usage
+
+Exclusive Lock for independent processes has the same API as the
+[threading.Lock](https://docs.python.org/3/library/threading.html#threading.Lock)
+for threads:
+```python
+import fasteners
+import threading
+
+lock = threading.Lock()                                 # for threads
+lock = fasteners.InterProcessLock('path/to/lock.file')  # for processes
+
+with lock:
+    ... # exclusive access
+
+# or alternatively    
+
+lock.acquire()
+... # exclusive access
+lock.release()
+```
+
+## Basic Reader Writer lock usage
+
+Reader Writer lock has a similar API, which is the same for threads or processes:
+
+```python
+import fasteners
+
+# for threads
+rw_lock = fasteners.ReaderWriterLock()                                 
+# for processes
+rw_lock = fasteners.InterProcessReaderWriterLock('path/to/lock.file')  
+
+with rw_lock.write_lock():
+    ... # write access
+
+with rw_lock.read_lock():
+    ... # read access
+
+# or alternatively, for processes only:
+
+rw_lock.acquire_read_lock()
+... # read access
+rw_lock.release_read_lock()
+
+rw_lock.acquire_write_lock()
+... # write access
+rw_lock.release_write_lock()
+```
+
+## Advanced usage
+
+For more details and options see [Process lock details](inter_process.md) and [Thread lock details](inter_thread.md).
+
diff --git a/docs/guide/inter_process.md b/docs/guide/inter_process.md
new file mode 100644
index 0000000..e38fbb4
--- /dev/null
+++ b/docs/guide/inter_process.md
@@ -0,0 +1,114 @@
+# Inter process locks
+
+Fasteners inter-process locks are cross-platform and are released automatically
+if the process crashes. They are based on the platform specific locking
+mechanisms:
+
+* fcntl for posix (Linux and OSX)
+* LockFileEx (via pywin32) and \_locking (via msvcrt) for Windows
+
+## Difference from `multiprocessing.Lock`
+
+Python standard library [multiprocessing.Lock] functions when the processes are
+launched by a single main process, who is responsible for managing the 
+synchronization. `fasteners` locks use the operating system mechanisms for 
+synchronization management, and hence work between processes that were launched
+independently.
+
+## Timeouts
+
+`fasteners` locks support timeouts, that can be used as follows:
+
+```python
+import fasteners
+
+lock = fasteners.InterProcessLock('path/to/lock.file')
+
+lock.acquire(timeout=10)
+... # exclusive access
+lock.release()
+```
+
+Equivalently for readers writer lock:
+
+
+```python
+import fasteners
+
+lock = fasteners.InterProcessReaderWriterLock('path/to/lock.file')
+
+lock.acquire_read_lock(timeout=10)
+... # exclusive access
+lock.release_read_lock()
+
+lock.acquire_write_lock(timeout=10)
+... # exclusive access
+lock.release_write_lock()
+```
+
+## Decorators
+
+For extra sugar, a function that always needs exclusive / read / write access
+can be decorated using one of the provided decorators. Note that they do not 
+expose the timeout parameter, and always block until the lock is acquired.
+
+```python
+import fasteners
+
+
+@fasteners.interprocess_read_locked
+def read_file():
+  ...
+
+@fasteners.interprocess_write_locked
+def write_file():
+  ...
+
+@fasteners.interprocess_locked
+def do_something_exclusive():
+  ...
+```
+
+## (Lack of) Features
+
+The intersection of fcntl and LockFileEx features is quite small, hence you
+should always assume that:
+
+* Locks are advisory. They do not prevent the modification of the locked file
+  by other processes.
+
+* Locks can be unintentionally released by simply opening and closing the file
+  descriptor, so lock files must be accessed only using provided abstractions.
+
+* Locks are not [reentrant]. An attempt to acquire a lock multiple times can
+  result in a deadlock or a crash upon a release of the lock.
+
+* Reader writer locks are not [upgradeable]. An attempt to get a reader's lock
+  while holding a writer's lock (or vice versa) can result in a deadlock or a
+  crash upon a release of the lock.
+
+* There are no guarantees regarding usage by multiple threads in a
+  single process. The locks work only between processes.
+
+## Resources
+
+To learn more about the complications of locking on different platforms we
+recommend the following resources:
+
+* [File locking in Linux (blog post)](https://gavv.github.io/articles/file-locks/)
+
+* [On the Brokenness of File Locking (blog post)](http://0pointer.de/blog/projects/locking.html)
+
+* [Everything you never wanted to know about file locking (blog post)](https://chris.improbable.org/2010/12/16/everything-you-never-wanted-to-know-about-file-locking/)
+
+* [Record Locking (course notes)](http://poincare.matf.bg.ac.rs/~ivana/courses/ps/sistemi_knjige/pomocno/apue/APUE/0201433079/ch14lev1sec3.html)
+
+* [Windows NT Files -- Locking (pywin32 docs)](http://timgolden.me.uk/pywin32-docs/Windows_NT_Files_.2d.2d_Locking.html)
+
+* [_locking (Windows Dev Center)](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/locking?view=vs-2019)
+
+* [LockFileEx function (Windows Dev Center)](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex)
+
+[upgradeable]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Upgradable_RW_lock>
+[reentrant]: https://en.wikipedia.org/wiki/Reentrant_mutex
+[multiprocessing.Lock]: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Lock
\ No newline at end of file
diff --git a/docs/guide/inter_thread.md b/docs/guide/inter_thread.md
new file mode 100644
index 0000000..4fa6062
--- /dev/null
+++ b/docs/guide/inter_thread.md
@@ -0,0 +1,50 @@
+# Inter thread locks
+
+Fasteners inter-thread locks were build specifically for the needs of
+`oslo.concurrency` project and thus have a rather peculiar API. We do not
+recommend using it fully, and it is hence not documented (but maintained until
+the end of time).
+
+Instead, we recommend limiting the use of fasteners inter-thread readers writer
+lock to the basic API:
+
+```python
+import fasteners
+
+# for threads
+rw_lock = fasteners.ReaderWriterLock()
+
+with rw_lock.write_lock():
+    ...  # write access
+
+with rw_lock.read_lock():
+    ...  # read access
+```
+
+## (Lack of) Features
+
+Fasteners inter-thread readers writer lock is
+
+* not [upgradeable]. An attempt to get a reader's lock while holding a writer's
+  lock (or vice versa) will raise an exception.
+
+* [reentrant] (!). You can acquire (and correspondingly release) the lock
+  multiple times.
+
+* has writer preference. Readers will queue after writers and pending writers.
+
+[upgradeable]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Upgradable_RW_lock>
+
+[reentrant]: https://en.wikipedia.org/wiki/Reentrant_mutex
+
+## Different thread creation mechanisms
+
+If your threads are created by some other means than the standard library `threading`
+module, you may need to provide corresponding thread identification and synchronisation
+functions to the `ReaderWriterLock`.
+
+### Eventlet
+
+In particular, in case of `eventlet` threads, you should monkey_patch the stdlib threads with `eventlet.monkey_patch(tread=True)`
+and initialise the `ReaderWriterLock` as `ReaderWriterLock(current_thread_functor=eventlet.getcurrent)`.
+
diff --git a/docs/img/favicon-192x192.png b/docs/img/favicon-192x192.png
new file mode 100644
index 0000000..4e5395d
Binary files /dev/null and b/docs/img/favicon-192x192.png differ
diff --git a/docs/img/favicon-32x32.png b/docs/img/favicon-32x32.png
new file mode 100644
index 0000000..818880b
Binary files /dev/null and b/docs/img/favicon-32x32.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..996d0c3
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,28 @@
+# Fasteners
+
+Python standard library provides an Exclusive Lock for threads and Exclusive Lock for processes
+spawned by `multiprocessing` module. `fasteners` provides additional three synchronization primitives:
+
+* Exclusive Lock for independent processes
+* Readers Writer Lock for independent processes
+* Readers Writer Lock for threads
+
+## Installation
+
+```
+pip install fasteners
+```
+
+## Usage
+
+See [User Guide](guide/index.md) for usage tips and examples and [Reference](api/inter_process.md) for detailed API.
+
+## Similar libraries
+
+[`portarlocker`](https://github.com/WoLpH/portalocker): readers writer lock and semaphore for 
+independent processes, exclusive lock based on redis. 
+
+[`py-filelock`](https://github.com/tox-dev/py-filelock): exclusive lock for independent processes.
+
+[`pyReaderWriterLock`](https://github.com/elarivie/pyReaderWriterLock): inter-thread readers writer 
+locks, optionally downgradable, with various priorities (reader, writer, fair).
\ No newline at end of file
diff --git a/fasteners/lock.py b/fasteners/lock.py
index 2cb76f1..6c872d4 100644
--- a/fasteners/lock.py
+++ b/fasteners/lock.py
@@ -21,110 +21,53 @@ import collections
 import contextlib
 import functools
 import threading
+from typing import Optional
 
 from fasteners import _utils
 
 
-def read_locked(*args, **kwargs):
-    """Acquires & releases a read lock around call into decorated method.
-
-    NOTE(harlowja): if no attribute name is provided then by default the
-    attribute named '_lock' is looked for (this attribute is expected to be
-    a :py:class:`.ReaderWriterLock`) in the instance object this decorator
-    is attached to.
-    """
-
-    def decorator(f):
-        attr_name = kwargs.get('lock', '_lock')
-
-        @functools.wraps(f)
-        def wrapper(self, *args, **kwargs):
-            rw_lock = getattr(self, attr_name)
-            with rw_lock.read_lock():
-                return f(self, *args, **kwargs)
-
-        return wrapper
-
-    # This is needed to handle when the decorator has args or the decorator
-    # doesn't have args, python is rather weird here...
-    if kwargs or not args:
-        return decorator
-    else:
-        if len(args) == 1:
-            return decorator(args[0])
-        else:
-            return decorator
-
-
-def write_locked(*args, **kwargs):
-    """Acquires & releases a write lock around call into decorated method.
-
-    NOTE(harlowja): if no attribute name is provided then by default the
-    attribute named '_lock' is looked for (this attribute is expected to be
-    a :py:class:`.ReaderWriterLock` object) in the instance object this
-    decorator is attached to.
-    """
-
-    def decorator(f):
-        attr_name = kwargs.get('lock', '_lock')
-
-        @functools.wraps(f)
-        def wrapper(self, *args, **kwargs):
-            rw_lock = getattr(self, attr_name)
-            with rw_lock.write_lock():
-                return f(self, *args, **kwargs)
-
-        return wrapper
-
-    # This is needed to handle when the decorator has args or the decorator
-    # doesn't have args, python is rather weird here...
-    if kwargs or not args:
-        return decorator
-    else:
-        if len(args) == 1:
-            return decorator(args[0])
-        else:
-            return decorator
-
-
 class ReaderWriterLock(object):
-    """A reader/writer lock.
-
-    This lock allows for simultaneous readers to exist but only one writer
-    to exist for use-cases where it is useful to have such types of locks.
-
-    Currently a reader can not escalate its read lock to a write lock and
-    a writer can not acquire a read lock while it is waiting on the write
-    lock.
+    """An inter-thread readers writer lock."""
 
-    In the future these restrictions may be relaxed.
-
-    This can be eventually removed if http://bugs.python.org/issue8800 ever
-    gets accepted into the python standard threading library...
-    """
-
-    #: Writer owner type/string constant.
-    WRITER = 'w'
-
-    #: Reader owner type/string constant.
-    READER = 'r'
+    WRITER = 'w'  #: Writer owner type/string constant.
+    READER = 'r'  #: Reader owner type/string constant.
 
     def __init__(self,
                  condition_cls=threading.Condition,
-                 current_thread_functor=None):
+                 current_thread_functor=threading.current_thread):
+        """
+        Args:
+            condition_cls:
+                Optional custom `Condition` primitive used for synchronization.
+            current_thread_functor:
+                Optional function that returns the identity of the thread in case
+                threads are not properly identified by threading.current_thread
+        """
         self._writer = None
         self._pending_writers = collections.deque()
         self._readers = {}
         self._cond = condition_cls()
-        self._current_thread = threading.current_thread
+        self._current_thread = current_thread_functor
 
     @property
-    def has_pending_writers(self):
-        """Returns if there are writers waiting to become the *one* writer."""
+    def has_pending_writers(self) -> bool:
+        """Check if there pending writers
+
+        Returns:
+            Whether there are pending writers.
+        """
         return bool(self._pending_writers)
 
-    def is_writer(self, check_pending=True):
-        """Returns if the caller is the active writer or a pending writer."""
+    def is_writer(self, check_pending: bool = True) -> bool:
+        """Check if caller is a writer (optionally pending writer).
+
+        Args:
+            check_pending:
+                Whether to check for pending writer status.
+
+        Returns:
+            Whether the caller is the active (or optionally pending) writer.
+        """
         me = self._current_thread()
         if self._writer == me:
             return True
@@ -133,8 +76,22 @@ class ReaderWriterLock(object):
         else:
             return False
 
+    def is_reader(self) -> bool:
+        """Check if caller is a reader.
+
+        Returns:
+            Whether the caller is an active reader.
+        """
+        me = self._current_thread()
+        return me in self._readers
+
     @property
-    def owner(self):
+    def owner(self) -> Optional[str]:
+        """Caller ownership (if any) of the lock
+
+        Returns:
+            `'w'` if caller is a writer, `'r'` if caller is a reader, None otherwise.
+        """
         """Returns whether the lock is locked by a writer or reader."""
         if self._writer is not None:
             return self.WRITER
@@ -142,19 +99,14 @@ class ReaderWriterLock(object):
             return self.READER
         return None
 
-    def is_reader(self):
-        """Returns if the caller is one of the readers."""
-        me = self._current_thread()
-        return me in self._readers
-
     @contextlib.contextmanager
     def read_lock(self):
         """Context manager that grants a read lock.
 
         Will wait until no active or pending writers.
 
-        Raises a ``RuntimeError`` if a pending writer tries to acquire
-        a read lock.
+        Raises:
+            RuntimeError: if a pending writer tries to acquire a read lock.
         """
         me = self._current_thread()
         if me in self._pending_writers:
@@ -202,8 +154,8 @@ class ReaderWriterLock(object):
 
         Guaranteed for locks to be processed in fair order (FIFO).
 
-        Raises a ``RuntimeError`` if an active reader attempts to acquire
-        a lock.
+        Raises:
+            RuntimeError: if an active reader attempts to acquire a lock.
         """
         me = self._current_thread()
         i_am_writer = self.is_writer(check_pending=False)
@@ -231,22 +183,6 @@ class ReaderWriterLock(object):
                     self._cond.notify_all()
 
 
-@contextlib.contextmanager
-def try_lock(lock):
-    """Attempts to acquire a lock, and auto releases if acquired (on exit)."""
-    # NOTE(harlowja): the keyword argument for 'blocking' does not work
-    # in py2.x and only is fixed in py3.x (this adjustment is documented
-    # and/or debated in http://bugs.python.org/issue10789); so we'll just
-    # stick to the format that works in both (oddly the keyword argument
-    # works in py2.x but only with reentrant locks).
-    was_locked = lock.acquire(False)
-    try:
-        yield was_locked
-    finally:
-        if was_locked:
-            lock.release()
-
-
 def locked(*args, **kwargs):
     """A locking **method** decorator.
 
@@ -264,6 +200,10 @@ def locked(*args, **kwargs):
     NOTE(harlowja): a custom logger (which will be used if lock release
     failures happen) can be provided by passing a logger instance for keyword
     argument ``logger``.
+
+    NOTE(paulius): This function is DEPRECATED and will be kept until the end
+    of time. It is potentially used by oslo, but too specific to be recommended
+    for other projects
     """
 
     def decorator(f):
@@ -296,3 +236,103 @@ def locked(*args, **kwargs):
             return decorator(args[0])
         else:
             return decorator
+
+
+def read_locked(*args, **kwargs):
+    """Acquires & releases a read lock around call into decorated method.
+
+    NOTE(harlowja): if no attribute name is provided then by default the
+    attribute named '_lock' is looked for (this attribute is expected to be
+    a :py:class:`.ReaderWriterLock`) in the instance object this decorator
+    is attached to.
+
+    NOTE(paulius): This function is DEPRECATED and will be kept until the end
+    of time. It is potentially used by oslo, but too specific to be recommended
+    for other projects
+    """
+
+    def decorator(f):
+        attr_name = kwargs.get('lock', '_lock')
+
+        @functools.wraps(f)
+        def wrapper(self, *args, **kwargs):
+            rw_lock = getattr(self, attr_name)
+            with rw_lock.read_lock():
+                return f(self, *args, **kwargs)
+
+        return wrapper
+
+    # This is needed to handle when the decorator has args or the decorator
+    # doesn't have args, python is rather weird here...
+    if kwargs or not args:
+        return decorator
+    else:
+        if len(args) == 1:
+            return decorator(args[0])
+        else:
+            return decorator
+
+
+def write_locked(*args, **kwargs):
+    """Acquires & releases a write lock around call into decorated method.
+
+    NOTE(harlowja): if no attribute name is provided then by default the
+    attribute named '_lock' is looked for (this attribute is expected to be
+    a :py:class:`.ReaderWriterLock` object) in the instance object this
+    decorator is attached to.
+
+    NOTE(paulius): This function is DEPRECATED and will be kept until the end
+    of time. It is potentially used by oslo, but too specific to be recommended
+    for other projects
+    """
+
+    def decorator(f):
+        attr_name = kwargs.get('lock', '_lock')
+
+        @functools.wraps(f)
+        def wrapper(self, *args, **kwargs):
+            rw_lock = getattr(self, attr_name)
+            with rw_lock.write_lock():
+                return f(self, *args, **kwargs)
+
+        return wrapper
+
+    # This is needed to handle when the decorator has args or the decorator
+    # doesn't have args, python is rather weird here...
+    if kwargs or not args:
+        return decorator
+    else:
+        if len(args) == 1:
+            return decorator(args[0])
+        else:
+            return decorator
+
+
+@contextlib.contextmanager
+def try_lock(lock: threading.Lock) -> bool:
+    """Context manager that attempts to acquire a lock without a timeout, and
+    releases it on exit (if acquired).
+
+    Args:
+        lock:
+            A lock to try to acquire.
+
+    Returns:
+        Whether the lock was acquired.
+
+    # NOTE(harlowja): the keyword argument for 'blocking' does not work
+    # in py2.x and only is fixed in py3.x (this adjustment is documented
+    # and/or debated in http://bugs.python.org/issue10789); so we'll just
+    # stick to the format that works in both (oddly the keyword argument
+    # works in py2.x but only with reentrant locks).
+
+    NOTE(paulius): This function is DEPRECATED and will be kept until the end
+    of time. It is potentially used by oslo, but too specific to be recommended
+    for other projects
+    """
+    was_locked = lock.acquire(False)
+    try:
+        yield was_locked
+    finally:
+        if was_locked:
+            lock.release()
diff --git a/fasteners/process_lock.py b/fasteners/process_lock.py
index fa4d789..1197731 100644
--- a/fasteners/process_lock.py
+++ b/fasteners/process_lock.py
@@ -20,10 +20,16 @@ import errno
 import functools
 import logging
 import os
+from pathlib import Path
 import threading
 import time
+from typing import Callable
+from typing import Optional
+from typing import Union
 
 from fasteners import _utils
+from fasteners.process_mechanism import _interprocess_mechanism
+from fasteners.process_mechanism import _interprocess_reader_writer_mechanism
 
 LOG = logging.getLogger(__name__)
 
@@ -49,24 +55,25 @@ def _ensure_tree(path):
         return True
 
 
-class _InterProcessLock(object):
+class InterProcessLock:
     """An interprocess lock."""
 
-    MAX_DELAY = 0.1
-    """
-    Default maximum delay we will wait to try to acquire the lock (when
-    it's busy/being held by another process).
-    """
-
-    DELAY_INCREMENT = 0.01
-    """
-    Default increment we will use (up to max delay) after each attempt before
-    next attempt to acquire the lock. For example if 3 attempts have been made
-    the calling thread will sleep (0.01 * 3) before the next attempt to
-    acquire the lock (and repeat).
-    """
+    MAX_DELAY = 0.1  # For backwards compatibility
+    DELAY_INCREMENT = 0.01  # For backwards compatibility
 
-    def __init__(self, path, sleep_func=time.sleep, logger=None):
+    def __init__(self,
+                 path: Union[Path, str],
+                 sleep_func: Callable[[float], None] = time.sleep,
+                 logger: Optional[logging.Logger] = None):
+        """
+        args:
+            path:
+                Path to the file that will be used for locking.
+            sleep_func:
+                Optional function to use for sleeping.
+            logger:
+                Optional logger to use for logging.
+        """
         self.lockfile = None
         self.path = _utils.canonicalize_path(path)
         self.acquired = False
@@ -106,25 +113,27 @@ class _InterProcessLock(object):
         if self.lockfile is None or self.lockfile.closed:
             self.lockfile = open(self.path, 'a')
 
-    def acquire(self, blocking=True,
-                delay=DELAY_INCREMENT, max_delay=MAX_DELAY,
-                timeout=None):
-        """Attempt to acquire the given lock.
-
-        :param blocking: whether to wait forever to try to acquire the lock
-        :type blocking: bool
-        :param delay: when blocking this is the delay time in seconds that
-                      will be added after each failed acquisition
-        :type delay: int/float
-        :param max_delay: the maximum delay to have (this limits the
-                          accumulated delay(s) added after each failed
-                          acquisition)
-        :type max_delay: int/float
-        :param timeout: an optional timeout (limits how long blocking
-                        will occur for)
-        :type timeout: int/float
-        :returns: whether or not the acquisition succeeded
-        :rtype: bool
+    def acquire(self,
+                blocking: bool = True,
+                delay: float = 0.01,
+                max_delay: float = 0.1,
+                timeout: Optional[float] = None) -> bool:
+        """Attempt to acquire the lock.
+
+        Args:
+            blocking:
+                Whether to wait to try to acquire the lock.
+            delay:
+                When `blocking`, starting delay as well as the delay increment
+                (in seconds).
+            max_delay:
+                When `blocking` the maximum delay in between attempts to
+                acquire (in seconds).
+            timeout:
+                When `blocking`, maximal waiting time (in seconds).
+
+        Returns:
+            whether or not the acquisition succeeded
         """
         if delay < 0:
             raise ValueError("Delay must be greater than or equal to zero")
@@ -156,7 +165,7 @@ class _InterProcessLock(object):
     def __enter__(self):
         gotten = self.acquire()
         if not gotten:
-            # This shouldn't happen, but just incase...
+            # This shouldn't happen, but just in case...
             raise threading.ThreadError("Unable to acquire a file lock"
                                         " on `%s` (when used as a"
                                         " context manager)" % self.path)
@@ -188,50 +197,70 @@ class _InterProcessLock(object):
         self.release()
 
     def exists(self):
-        """Checks if the path that this lock exists at actually exists."""
         return os.path.exists(self.path)
 
     def trylock(self):
-        self._trylock(self.lockfile)
+        _interprocess_mechanism.trylock(self.lockfile)
 
     def unlock(self):
-        self._unlock(self.lockfile)
+        _interprocess_mechanism.unlock(self.lockfile)
 
-    @staticmethod
-    def _trylock(lockfile):
-        raise NotImplementedError()
 
-    @staticmethod
-    def _unlock(lockfile):
-        raise NotImplementedError()
-
-
-class _InterProcessReaderWriterLock(object):
+class InterProcessReaderWriterLock:
     """An interprocess readers writer lock."""
 
-    MAX_DELAY = 0.1
-    """
-    Default maximum delay we will wait to try to acquire the lock (when
-    it's busy/being held by another process).
-    """
-
-    DELAY_INCREMENT = 0.01
-    """
-    Default increment we will use (up to max delay) after each attempt before
-    next attempt to acquire the lock. For example if 3 attempts have been made
-    the calling thread will sleep (0.01 * 3) before the next attempt to
-    acquire the lock (and repeat).
-    """
+    MAX_DELAY = 0.1  # for backwards compatibility
+    DELAY_INCREMENT = 0.01  # for backwards compatibility
 
-    def __init__(self, path, sleep_func=time.sleep, logger=None):
+    def __init__(self,
+                 path: Union[Path, str],
+                 sleep_func: Callable[[float], None] = time.sleep,
+                 logger: Optional[logging.Logger] = None):
+        """
+        Args:
+            path:
+                Path to the file that will be used for locking.
+            sleep_func:
+                Optional function to use for sleeping.
+            logger:
+                Optional logger to use for logging.
+        """
         self.lockfile = None
         self.path = _utils.canonicalize_path(path)
         self.sleep_func = sleep_func
         self.logger = _utils.pick_first_not_none(logger, LOG)
 
+    @contextmanager
+    def read_lock(self, delay=0.01, max_delay=0.1):
+        """Context manager that grans a read lock"""
+
+        self.acquire_read_lock(blocking=True, delay=delay,
+                               max_delay=max_delay, timeout=None)
+        try:
+            yield
+        finally:
+            self.release_read_lock()
+
+    @contextmanager
+    def write_lock(self, delay=0.01, max_delay=0.1):
+        """Context manager that grans a write lock"""
+
+        gotten = self.acquire_write_lock(blocking=True, delay=delay,
+                                         max_delay=max_delay, timeout=None)
+
+        if not gotten:
+            # This shouldn't happen, but just in case...
+            raise threading.ThreadError("Unable to acquire a file lock"
+                                        " on `%s` (when used as a"
+                                        " context manager)" % self.path)
+        try:
+            yield
+        finally:
+            self.release_write_lock()
+
     def _try_acquire(self, blocking, watch, exclusive):
         try:
-            gotten = self._trylock(self.lockfile, exclusive)
+            gotten = _interprocess_reader_writer_mechanism.trylock(self.lockfile, exclusive)
         except Exception as e:
             raise threading.ThreadError(
                 "Unable to acquire lock on {} due to {}!".format(self.path, e))
@@ -252,56 +281,58 @@ class _InterProcessReaderWriterLock(object):
                 self.logger.log(_utils.BLATHER,
                                 'Created lock base path `%s`', basedir)
         if self.lockfile is None:
-            self.lockfile = self._get_handle(self.path)
-
-    def acquire_read_lock(self, blocking=True,
-                          delay=DELAY_INCREMENT, max_delay=MAX_DELAY,
-                          timeout=None):
+            self.lockfile = _interprocess_reader_writer_mechanism.get_handle(self.path)
 
+    def acquire_read_lock(self,
+                          blocking: bool = True,
+                          delay: float = 0.01,
+                          max_delay: float = 0.1,
+                          timeout: float = None) -> bool:
         """Attempt to acquire a reader's lock.
 
-        :param blocking: whether to wait forever to try to acquire the lock
-        :type blocking: bool
-        :param delay: when blocking this is the delay time in seconds that
-                      will be added after each failed acquisition
-        :type delay: int/float
-        :param max_delay: the maximum delay to have (this limits the
-                          accumulated delay(s) added after each failed
-                          acquisition)
-        :type max_delay: int/float
-        :param timeout: an optional timeout (limits how long blocking
-                        will occur for)
-        :type timeout: int/float
-        :returns: whether or not the acquisition succeeded
-        :rtype: bool
+        Args:
+            blocking:
+                Whether to wait to try to acquire the lock.
+            delay:
+                When `blocking`, starting delay as well as the delay increment
+                (in seconds).
+            max_delay:
+                When `blocking` the maximum delay in between attempts to
+                acquire (in seconds).
+            timeout:
+                When `blocking`, maximal waiting time (in seconds).
+
+        Returns:
+            whether or not the acquisition succeeded
         """
         return self._acquire(blocking, delay, max_delay, timeout, exclusive=False)
 
-    def acquire_write_lock(self, blocking=True,
-                           delay=DELAY_INCREMENT, max_delay=MAX_DELAY,
-                           timeout=None):
-
+    def acquire_write_lock(self,
+                           blocking: bool = True,
+                           delay: float = 0.01,
+                           max_delay: float = 0.1,
+                           timeout: float = None) -> bool:
         """Attempt to acquire a writer's lock.
 
-        :param blocking: whether to wait forever to try to acquire the lock
-        :type blocking: bool
-        :param delay: when blocking this is the delay time in seconds that
-                      will be added after each failed acquisition
-        :type delay: int/float
-        :param max_delay: the maximum delay to have (this limits the
-                          accumulated delay(s) added after each failed
-                          acquisition)
-        :type max_delay: int/float
-        :param timeout: an optional timeout (limits how long blocking
-                        will occur for)
-        :type timeout: int/float
-        :returns: whether or not the acquisition succeeded
-        :rtype: bool
+        Args:
+            blocking:
+                Whether to wait to try to acquire the lock.
+            delay:
+                When `blocking`, starting delay as well as the delay increment
+                (in seconds).
+            max_delay:
+                When `blocking` the maximum delay in between attempts to
+                acquire (in seconds).
+            timeout:
+                When `blocking`, maximal waiting time (in seconds).
+
+        Returns:
+            whether or not the acquisition succeeded
         """
         return self._acquire(blocking, delay, max_delay, timeout, exclusive=True)
 
     def _acquire(self, blocking=True,
-                 delay=DELAY_INCREMENT, max_delay=MAX_DELAY,
+                 delay=0.01, max_delay=0.1,
                  timeout=None, exclusive=True):
 
         if delay < 0:
@@ -327,13 +358,13 @@ class _InterProcessReaderWriterLock(object):
 
     def _do_close(self):
         if self.lockfile is not None:
-            self._close_handle(self.lockfile)
+            _interprocess_reader_writer_mechanism.close_handle(self.lockfile)
             self.lockfile = None
 
     def release_write_lock(self):
         """Release the writer's lock."""
         try:
-            self._unlock(self.lockfile)
+            _interprocess_reader_writer_mechanism.unlock(self.lockfile)
         except IOError:
             self.logger.exception("Could not unlock the acquired lock opened"
                                   " on `%s`", self.path)
@@ -351,7 +382,7 @@ class _InterProcessReaderWriterLock(object):
     def release_read_lock(self):
         """Release the reader's lock."""
         try:
-            self._unlock(self.lockfile)
+            _interprocess_reader_writer_mechanism.unlock(self.lockfile)
         except IOError:
             self.logger.exception("Could not unlock the acquired lock opened"
                                   " on `%s`", self.path)
@@ -366,168 +397,14 @@ class _InterProcessReaderWriterLock(object):
                                 "Unlocked and closed file lock open on"
                                 " `%s`", self.path)
 
-    @contextmanager
-    def write_lock(self, delay=DELAY_INCREMENT, max_delay=MAX_DELAY):
-
-        gotten = self.acquire_write_lock(blocking=True, delay=delay,
-                                         max_delay=max_delay, timeout=None)
 
-        if not gotten:
-            # This shouldn't happen, but just in case...
-            raise threading.ThreadError("Unable to acquire a file lock"
-                                        " on `%s` (when used as a"
-                                        " context manager)" % self.path)
-        try:
-            yield
-        finally:
-            self.release_write_lock()
-
-    @contextmanager
-    def read_lock(self, delay=DELAY_INCREMENT, max_delay=MAX_DELAY):
-
-        self.acquire_read_lock(blocking=True, delay=delay,
-                               max_delay=max_delay, timeout=None)
-        try:
-            yield
-        finally:
-            self.release_read_lock()
-
-    @staticmethod
-    def _trylock(lockfile, exclusive):
-        raise NotImplementedError()
-
-    @staticmethod
-    def _unlock(lockfile):
-        raise NotImplementedError()
-
-    @staticmethod
-    def _get_handle(path):
-        raise NotImplementedError()
-
-    @staticmethod
-    def _close_handle(lockfile):
-        raise NotImplementedError()
-
-
-class _WindowsLock(_InterProcessLock):
-    """Interprocess lock implementation that works on windows systems."""
-
-    @staticmethod
-    def _trylock(lockfile):
-        fileno = lockfile.fileno()
-        msvcrt.locking(fileno, msvcrt.LK_NBLCK, 1)
-
-    @staticmethod
-    def _unlock(lockfile):
-        fileno = lockfile.fileno()
-        msvcrt.locking(fileno, msvcrt.LK_UNLCK, 1)
-
-
-class _FcntlLock(_InterProcessLock):
-    """Interprocess lock implementation that works on posix systems."""
-
-    @staticmethod
-    def _trylock(lockfile):
-        fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
-
-    @staticmethod
-    def _unlock(lockfile):
-        fcntl.lockf(lockfile, fcntl.LOCK_UN)
-
-
-class _WindowsInterProcessReaderWriterLock(_InterProcessReaderWriterLock):
-    """Interprocess readers writer lock implementation that works on windows
-    systems."""
-
-    @staticmethod
-    def _trylock(lockfile, exclusive):
-
-        if exclusive:
-            flags = win32con.LOCKFILE_FAIL_IMMEDIATELY | win32con.LOCKFILE_EXCLUSIVE_LOCK
-        else:
-            flags = win32con.LOCKFILE_FAIL_IMMEDIATELY
-
-        handle = msvcrt.get_osfhandle(lockfile.fileno())
-        ok = win32file.LockFileEx(handle, flags, 0, 1, 0, win32file.pointer(pywintypes.OVERLAPPED()))
-        if ok:
-            return True
-        else:
-            last_error = win32file.GetLastError()
-            if last_error == win32file.ERROR_LOCK_VIOLATION:
-                return False
-            else:
-                raise OSError(last_error)
-
-    @staticmethod
-    def _unlock(lockfile):
-        handle = msvcrt.get_osfhandle(lockfile.fileno())
-        ok = win32file.UnlockFileEx(handle, 0, 1, 0, win32file.pointer(pywintypes.OVERLAPPED()))
-        if not ok:
-            raise OSError(win32file.GetLastError())
-
-    @staticmethod
-    def _get_handle(path):
-        return open(path, 'a+')
-
-    @staticmethod
-    def _close_handle(lockfile):
-        lockfile.close()
-
-
-class _FcntlInterProcessReaderWriterLock(_InterProcessReaderWriterLock):
-    """Interprocess readers writer lock implementation that works on posix
-    systems."""
-
-    @staticmethod
-    def _trylock(lockfile, exclusive):
-
-        if exclusive:
-            flags = fcntl.LOCK_EX | fcntl.LOCK_NB
-        else:
-            flags = fcntl.LOCK_SH | fcntl.LOCK_NB
-
-        try:
-            fcntl.lockf(lockfile, flags)
-            return True
-        except (IOError, OSError) as e:
-            if e.errno in (errno.EACCES, errno.EAGAIN):
-                return False
-            else:
-                raise e
-
-    @staticmethod
-    def _unlock(lockfile):
-        fcntl.lockf(lockfile, fcntl.LOCK_UN)
-
-    @staticmethod
-    def _get_handle(path):
-        return open(path, 'a+')
-
-    @staticmethod
-    def _close_handle(lockfile):
-        lockfile.close()
-
-
-if os.name == 'nt':
-    import msvcrt
-    import fasteners.pywin32.pywintypes as pywintypes
-    import fasteners.pywin32.win32con as win32con
-    import fasteners.pywin32.win32file as win32file
-
-    InterProcessLock = _WindowsLock
-    InterProcessReaderWriterLock = _WindowsInterProcessReaderWriterLock
-
-else:
-    import fcntl
-
-    InterProcessLock = _FcntlLock
-    InterProcessReaderWriterLock = _FcntlInterProcessReaderWriterLock
-
-
-def interprocess_write_locked(path):
-    """Acquires & releases an interprocess read lock around the call into
-    the decorated function"""
+def interprocess_write_locked(path: Union[Path, str]):
+    """Acquires & releases an interprocess  **write** lock around the call into
+    the decorated function
 
+    Args:
+        path: Path to the file used for locking.
+    """
     lock = InterProcessReaderWriterLock(path)
 
     def decorator(f):
@@ -541,10 +418,13 @@ def interprocess_write_locked(path):
     return decorator
 
 
-def interprocess_read_locked(path):
-    """Acquires & releases an interprocess read lock around the call into
-    the decorated function"""
+def interprocess_read_locked(path: Union[Path, str]):
+    """Acquires & releases an interprocess **read** lock around the call into
+    the decorated function
 
+    Args:
+        path: Path to the file used for locking.
+    """
     lock = InterProcessReaderWriterLock(path)
 
     def decorator(f):
@@ -558,10 +438,13 @@ def interprocess_read_locked(path):
     return decorator
 
 
-def interprocess_locked(path):
-    """Acquires & releases a interprocess lock around call into
-       decorated function."""
+def interprocess_locked(path: Union[Path, str]):
+    """Acquires & releases an interprocess lock around the call to the
+    decorated function.
 
+    Args:
+        path: Path to the file used for locking.
+   """
     lock = InterProcessLock(path)
 
     def decorator(f):
diff --git a/fasteners/process_mechanism.py b/fasteners/process_mechanism.py
new file mode 100644
index 0000000..39ccec6
--- /dev/null
+++ b/fasteners/process_mechanism.py
@@ -0,0 +1,154 @@
+from abc import ABC
+from abc import abstractmethod
+import errno
+import os
+
+
+class _InterProcessReaderWriterLockMechanism(ABC):
+
+    @staticmethod
+    @abstractmethod
+    def trylock(lockfile, exclusive):
+        ...
+
+    @staticmethod
+    @abstractmethod
+    def unlock(lockfile):
+        ...
+
+    @staticmethod
+    @abstractmethod
+    def get_handle(path):
+        ...
+
+    @staticmethod
+    @abstractmethod
+    def close_handle(lockfile):
+        ...
+
+
+class _InterProcessMechanism(ABC):
+    @staticmethod
+    @abstractmethod
+    def trylock(lockfile):
+        ...
+
+    @staticmethod
+    @abstractmethod
+    def unlock(lockfile):
+        ...
+
+
+class _WindowsInterProcessMechanism(_InterProcessMechanism):
+    """Interprocess lock implementation that works on windows systems."""
+
+    @staticmethod
+    def trylock(lockfile):
+        fileno = lockfile.fileno()
+        msvcrt.locking(fileno, msvcrt.LK_NBLCK, 1)
+
+    @staticmethod
+    def unlock(lockfile):
+        fileno = lockfile.fileno()
+        msvcrt.locking(fileno, msvcrt.LK_UNLCK, 1)
+
+
+class _FcntlInterProcessMechanism(_InterProcessMechanism):
+    """Interprocess lock implementation that works on posix systems."""
+
+    @staticmethod
+    def trylock(lockfile):
+        fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
+
+    @staticmethod
+    def unlock(lockfile):
+        fcntl.lockf(lockfile, fcntl.LOCK_UN)
+
+
+class _WindowsInterProcessReaderWriterLockMechanism(_InterProcessReaderWriterLockMechanism):
+    """Interprocess readers writer lock implementation that works on windows
+    systems."""
+
+    @staticmethod
+    def trylock(lockfile, exclusive):
+
+        if exclusive:
+            flags = win32con.LOCKFILE_FAIL_IMMEDIATELY | win32con.LOCKFILE_EXCLUSIVE_LOCK
+        else:
+            flags = win32con.LOCKFILE_FAIL_IMMEDIATELY
+
+        handle = msvcrt.get_osfhandle(lockfile.fileno())
+        ok = win32file.LockFileEx(handle, flags, 0, 1, 0, win32file.pointer(pywintypes.OVERLAPPED()))
+        if ok:
+            return True
+        else:
+            last_error = win32file.GetLastError()
+            if last_error == win32file.ERROR_LOCK_VIOLATION:
+                return False
+            else:
+                raise OSError(last_error)
+
+    @staticmethod
+    def unlock(lockfile):
+        handle = msvcrt.get_osfhandle(lockfile.fileno())
+        ok = win32file.UnlockFileEx(handle, 0, 1, 0, win32file.pointer(pywintypes.OVERLAPPED()))
+        if not ok:
+            raise OSError(win32file.GetLastError())
+
+    @staticmethod
+    def get_handle(path):
+        return open(path, 'a+')
+
+    @staticmethod
+    def close_handle(lockfile):
+        lockfile.close()
+
+
+class _FcntlInterProcessReaderWriterLockMechanism(_InterProcessReaderWriterLockMechanism):
+    """Interprocess readers writer lock implementation that works on posix
+    systems."""
+
+    @staticmethod
+    def trylock(lockfile, exclusive):
+
+        if exclusive:
+            flags = fcntl.LOCK_EX | fcntl.LOCK_NB
+        else:
+            flags = fcntl.LOCK_SH | fcntl.LOCK_NB
+
+        try:
+            fcntl.lockf(lockfile, flags)
+            return True
+        except (IOError, OSError) as e:
+            if e.errno in (errno.EACCES, errno.EAGAIN):
+                return False
+            else:
+                raise e
+
+    @staticmethod
+    def unlock(lockfile):
+        fcntl.lockf(lockfile, fcntl.LOCK_UN)
+
+    @staticmethod
+    def get_handle(path):
+        return open(path, 'a+')
+
+    @staticmethod
+    def close_handle(lockfile):
+        lockfile.close()
+
+
+if os.name == 'nt':
+    import msvcrt
+    import fasteners.pywin32.pywintypes as pywintypes
+    import fasteners.pywin32.win32con as win32con
+    import fasteners.pywin32.win32file as win32file
+
+    _interprocess_reader_writer_mechanism = _WindowsInterProcessReaderWriterLockMechanism()
+    _interprocess_mechanism = _WindowsInterProcessMechanism()
+
+else:
+    import fcntl
+
+    _interprocess_reader_writer_mechanism = _FcntlInterProcessReaderWriterLockMechanism()
+    _interprocess_mechanism = _FcntlInterProcessMechanism()
diff --git a/fasteners/version.py b/fasteners/version.py
index 88fc0e6..3ec34f5 100644
--- a/fasteners/version.py
+++ b/fasteners/version.py
@@ -17,7 +17,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-_VERSION = "0.17.3"
+_VERSION = "0.18"
 
 
 def version_string():
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..cf38408
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,48 @@
+site_name: Fasteners
+site_url: https://fasteners.readthedocs.io/
+repo_url: https://github.com/harlowja/fasteners
+edit_uri: ''
+
+nav:
+  - Home: index.md
+  - User Guide:
+      - guide/index.md
+      - Process locks: guide/inter_process.md
+      - Thread locks: guide/inter_thread.md
+      - Glossary: guide/glossary.md
+  - Reference:
+      - Process locks: api/inter_process.md
+      - Thread locks: api/inter_thread.md
+  - Changelog: CHANGELOG.md
+
+theme:
+  name: material
+  features:
+    - navigation.indexes
+    - navigation.instant
+    - navigation.tracking
+  logo: img/favicon-192x192.png
+  favicon: img/favicon-32x32.png
+
+plugins:
+  - search
+  - mkdocstrings:
+      default_handler: python
+      handlers:
+        python:
+          rendering:
+            show_category_heading: true
+            show_source: false
+            members_order: source
+            show_if_no_docstring: false
+            show_root_full_path: false
+            show_root_heading: true
+      watch:
+        - fasteners
+
+markdown_extensions:
+  - pymdownx.highlight:
+      anchor_linenums: true
+  - pymdownx.inlinehilite
+  - pymdownx.snippets
+  - pymdownx.superfences
\ No newline at end of file
diff --git a/publish.md b/publish.md
index df0de81..e482dee 100644
--- a/publish.md
+++ b/publish.md
@@ -5,7 +5,7 @@
 2. Update the version number:
 
        setup.cfg
-       fasteners/version
+       fasteners/version.py
 
 4. Make sure that the working directory is clean.
 
@@ -22,4 +22,6 @@
 
        twine upload dist/*
 
-7. Tag the git repo.
\ No newline at end of file
+7. Tag the git repo.
+
+8. Read the docs will be updated automatically.
diff --git a/requirements-docs.txt b/requirements-docs.txt
index cbf1e36..cd7299f 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -1,2 +1,3 @@
-sphinx
-sphinx-rtd-theme
+mkdocs
+mkdocstrings[python] >= 0.18
+mkdocs-material
diff --git a/requirements-test.txt b/requirements-test.txt
index 38cd410..8c77595 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,4 +1,4 @@
 diskcache
+eventlet
 more_itertools
-futures
 pytest
diff --git a/setup.cfg b/setup.cfg
index 796790e..348890e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = fasteners
-version = 0.17.3
+version = 0.18
 url = https://github.com/harlowja/fasteners
 
 author = Joshua Harlow
@@ -33,7 +33,8 @@ license_files = LICENSE
 [options]
 packages = find:
 python_requires = >=3.6
-include_package_data = True
 
 [options.packages.find]
-exclude = tests
+exclude =
+    tests
+    tests_eventlet
diff --git a/tests/test_process_lock.py b/tests/test_process_lock.py
index 7aa61c7..c9b793e 100644
--- a/tests/test_process_lock.py
+++ b/tests/test_process_lock.py
@@ -28,6 +28,7 @@ import time
 import pytest
 
 from fasteners import process_lock as pl
+from fasteners.process_mechanism import _interprocess_mechanism
 
 WIN32 = os.name == 'nt'
 
@@ -151,9 +152,9 @@ def _lock_files(lock_path, handles_dir, num_handles=50):
         count = 0
         for handle in handles:
             try:
-                pl.InterProcessLock._trylock(handle)
+                _interprocess_mechanism.trylock(handle)
                 count += 1
-                pl.InterProcessLock._unlock(handle)
+                _interprocess_mechanism.unlock(handle)
             except IOError:
                 sys.exit(2)
             finally:
diff --git a/tests_eventlet/__init__.py b/tests_eventlet/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests_eventlet/test_eventlet.py b/tests_eventlet/test_eventlet.py
new file mode 100644
index 0000000..8d0f1f7
--- /dev/null
+++ b/tests_eventlet/test_eventlet.py
@@ -0,0 +1,41 @@
+import eventlet
+
+eventlet.monkey_patch(thread=True)
+
+from fasteners import ReaderWriterLock
+
+
+def test_eventlet_spawn_n_bug():
+    """Both threads run at the same time thru the lock"""
+    STARTED = eventlet.event.Event()
+    FINISHED = eventlet.event.Event()
+    lock = ReaderWriterLock()
+
+    def other():
+        STARTED.send('started')
+        with lock.write_lock():
+            FINISHED.send('finished')
+
+    with lock.write_lock():
+        eventlet.spawn_n(other)
+        STARTED.wait()
+        assert FINISHED.wait(1) == 'finished'
+
+
+def test_eventlet_spawn_n_bugfix():
+    """Threads wait for each other as they should"""
+    STARTED = eventlet.event.Event()
+    FINISHED = eventlet.event.Event()
+    lock = ReaderWriterLock(current_thread_functor=eventlet.getcurrent)
+
+    def other():
+        STARTED.send('started')
+        with lock.write_lock():
+            FINISHED.send('finished')
+
+    with lock.write_lock():
+        eventlet.spawn_n(other)
+        STARTED.wait()
+        assert FINISHED.wait(1) is None
+
+    assert FINISHED.wait(1) == 'finished'

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.18.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.18.dist-info/RECORD
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.18.dist-info/WHEEL
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.18.dist-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners/process_mechanism.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.17.3.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.17.3.dist-info/RECORD
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.17.3.dist-info/WHEEL
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/fasteners-0.17.3.dist-info/top_level.txt

No differences were encountered in the control files

More details

Full run details