diff --git a/.gitignore b/.gitignore
index 062e64452ee2963800bd724fb9b72010275c8aa7..3acd50dec03a25f933769ea4fb99291de3e99201 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
 #################
 
 satella.sublime*
-
+hs_err_pid*.log
 *.pydevproject
 .project
 .metadata
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ac80439596edf0b79c54c41f5324d059092a6e2..16a700b4bec811745937bf9c05b35184435dcc43 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
 # v2.2.3
 
-* _TBA_
+* renamed `AcquirePIDLock` to `PIDFileLock`
+* more unit tests for `PIDFileLock`
+    * it finally works
 
 # v2.2.2
 
diff --git a/MANIFEST.in b/MANIFEST.in
index cfabbb68818ec823c5b63874a7c751fc79008540..373701c7be122e140c98812366e8d058fe46ef6e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,3 @@
 include LICENSE
 include README.md
-include requirements.txt
 include CHANGELOG.md
diff --git a/README.md b/README.md
index 91dc6379a0911cf8bc88e2132253725234fbf603..f591be911571b25a6633673c6ac676c015ef85da 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,9 @@ _satella.posix_ in unavailable on non-POSIX systems, but the rest should work OK
 Full [documentation](http://satella.readthedocs.io/en/latest/?badge=latest)
 is available for the brave souls that do decide to use this library.
 
-Satella is a Python 3.5+ library for writing server applications, especially those dealing with mundane but useful things.
+Satella is a zero-requirements Python 3.5+ library for writing
+server applications, especially those dealing with mundane but
+useful things. It also runs on PyPy.
 
 See [LICENSE](LICENSE) for text of the license.
 
diff --git a/docs/posix.rst b/docs/posix.rst
index 7e9e789f6ff6525141eb400bd4fa941d562b42dc..e8f0dd498cec035c1b1735c1c9991e035af27448 100644
--- a/docs/posix.rst
+++ b/docs/posix.rst
@@ -23,3 +23,13 @@ Return if running as root
 
 .. autofunction:: satella.posix.is_running_as_root
 
+
+PIDFileLock
+--------------
+
+This is meant to acquire a lock on a file.
+
+.. autoclass:: satella.posix.PIDFileLock
+    :members:
+
+.. autoclass:: satella.posix.LockIsHeld
diff --git a/requirements.txt b/requirements.txt
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4d92cc08db6a0d8bfedbbbd620d1fb11f84677b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -0,0 +1 @@
+psutil
diff --git a/satella/__init__.py b/satella/__init__.py
index 94657e466552e17619a3f113ad3c2f17bd78b5a1..9749a6596ac3a8d5aab0baf0f9e00b14f673e76f 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1,2 +1,2 @@
 # coding=UTF-8
-__version__ = '2.2.3a1'
+__version__ = '2.2.3'
diff --git a/satella/configuration/__init__.py b/satella/configuration/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d2c95b57e6b4b6cb4729cd8bec8d95f768068a3 100644
--- a/satella/configuration/__init__.py
+++ b/satella/configuration/__init__.py
@@ -0,0 +1,4 @@
+from . import schema
+from . import sources
+
+__all__ = ['schema', 'sources']
diff --git a/satella/posix/__init__.py b/satella/posix/__init__.py
index cf61df578f184e9049577c6eb23b49a8b4153163..b38c9099c16e2d6336e725535de369a5561c5fb0 100644
--- a/satella/posix/__init__.py
+++ b/satella/posix/__init__.py
@@ -5,12 +5,12 @@ POSIX things
 import os
 
 from .daemon import daemonize
-from .pidlock import AcquirePIDLock
+from .pidlock import PIDFileLock, LockIsHeld
 from .signals import hang_until_sig
 
 __all__ = [
     'daemonize',
-    'AcquirePIDLock',
+    'PIDFileLock', 'LockIsHeld',
     'hang_until_sig',
     'is_running_as_root',
     'suicide'
diff --git a/satella/posix/pidlock.py b/satella/posix/pidlock.py
index 30ded11ad61c59c27fedd3dba35d5229c6a2fefe..39e1d0d3c8e4bb21dca74665d937bd1f0d12fe5a 100644
--- a/satella/posix/pidlock.py
+++ b/satella/posix/pidlock.py
@@ -1,109 +1,95 @@
 import logging
 import os
 
-logger = logging.getLogger(__name__)
-
+import psutil
 
-class FailedToAcquire(Exception):
-    """Failed to acquire the process lock file"""
+logger = logging.getLogger(__name__)
 
 
-class LockIsHeld(FailedToAcquire):
+class LockIsHeld(Exception):
     """
     Lock is held by someone
 
-    Has two attributes:
-        pid - integer - PID of the holder
-        is_alive - bool - whether the holder is an alive process
+    pid -- PID of the holder, who is alive
     """
 
+    def __init__(self, pid):
+        self.pid = pid
+
 
-class AcquirePIDLock:
+class PIDFileLock:
     """
     Acquire a PID lock file.
 
+    Usable also on Windows
+
     Usage:
 
-    >>> with AcquirePIDLock('myservice.pid'):
+    >>> with PIDFileLock('myservice.pid'):
     >>>     ... rest of code ..
 
     Or alternatively
 
-    >>> pid_lock = AcquirePIDLock('myservice.pid')
+    >>> pid_lock = PIDFileLock('myservice.pid')
     >>> pid_lock.acquire()
     >>> ...
     >>> pid_lock.release()
 
     The constructor doesn't throw, __enter__ or acquire() does, one of:
 
-    * AcquirePIDLock.FailedToAcquire - base class for errors. Thrown if can't read the file
-    * AcquirePIDLock.LockIsHeld - lock is already held. This has two attributes - pid (int), the PID of holder,
+   * LockIsHeld - lock is already held. This has two attributes - pid (int), the PID of holder,
                                   and is_alive (bool) - whether the holder is an alive process
     """
 
-    def __init__(self, pid, is_alive):
-        self.pid = pid
-        self.is_alive = is_alive
-
-    def __init__(self, pid_file, base_dir=u'/var/run', delete_on_dead=False):
+    def __init__(self, pid_file, base_dir=u'/var/run'):
         """
         Initialize a PID lock file object
 
         :param pid_file: rest of path
         :param base_dir: base lock directory
-        :param delete_on_dead: delete the lock file if holder is dead, and retry
         """
-        self.delete_on_dead = delete_on_dead
-
         self.path = os.path.join(base_dir, pid_file)
-
-        self.fileno = None
+        self.file_no = None
 
     def release(self):
-        if self.fileno is not None:
-            os.close(self.fileno)
+        """
+        Free the lock
+        """
+        if self.file_no is not None:
             os.unlink(self.path)
+            self.file_no = None
 
     def acquire(self):
         """
         Acquire the PID lock
 
         :raises LockIsHeld: if lock if held
-        :raises FailedToAcquire: if for example a directory exists in that place
         """
         try:
-            self.fileno = os.open(self.path, os.O_CREAT | os.O_EXCL)
-        except (IOError, OSError):
-            try:
-                with open(self.path, 'rb') as flock:
-                    try:
-                        pid = int(flock.read())
-                    except ValueError:
-                        logger.warning(
-                            'PID file found but doesn''t have an int, skipping')
-                        return
-            except IOError as e:
-                raise FailedToAcquire(repr(e))
-
-            # Is this process alive?
-            try:
-                os.kill(pid, 0)
-            except OSError:  # dead
-                raise LockIsHeld(pid, False)
-            else:
-                raise LockIsHeld(pid, True)
+            self.file_no = os.open(self.path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+        except (OSError, FileExistsError):
+            with open(self.path, 'r') as fin:
+                data = fin.read().strip()
 
-    def __enter__(self):
-        try:
-            self.acquire()
-        except LockIsHeld as e:
-            if self.delete_on_dead and (not e.is_alive):
+            try:
+                pid = int(data)
+            except ValueError:
                 os.unlink(self.path)
-                self.acquire()
+                return self.acquire()
+
+            if pid in {x.pid for x in psutil.process_iter()}:
+                raise LockIsHeld(pid)
             else:
-                raise
+                # does not exist
+                os.unlink(self.path)
+                return self.acquire()
 
-        self.success = True
+        fd = os.fdopen(self.file_no, 'w')
+        fd.write(str(os.getpid()) + '\n')
+        fd.close()
+
+    def __enter__(self):
+        self.acquire()
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.release()
diff --git a/setup.py b/setup.py
index 594a2324b2f220ec7e114e8a12973ab03370bd6d..620a3d4b369288c6f567c6f9ef78b3224a5ad455 100644
--- a/setup.py
+++ b/setup.py
@@ -7,10 +7,11 @@ setup(keywords=['ha', 'high availability', 'scalable', 'scalability', 'server'],
       packages=find_packages(include=['satella', 'satella.*']),
       version=__version__,
       install_requires=[
+            'psutil'
       ],
       tests_require=[
           "nose", "mock", "coverage"
       ],
       test_suite='nose.collector',
-      python_requires='!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*'
+      python_requires='!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
       )
diff --git a/tests/test_configuration/test_sources/test_envvars.py b/tests/test_configuration/test_sources/test_envvars.py
index 66a22444a4b021c78ec926473afbb6681c64b52b..37a40f06ec9fe8c2744378c5115481604fe2a6ce 100644
--- a/tests/test_configuration/test_sources/test_envvars.py
+++ b/tests/test_configuration/test_sources/test_envvars.py
@@ -1,3 +1,4 @@
+from satella.configuration import sources
 from satella.configuration.sources import EnvVarsSource, OptionalSource, AlternativeSource, EnvironmentSource, \
     StaticSource, MergingSource
 from .utils import SourceTestCase, mock_env
@@ -6,7 +7,7 @@ from .utils import SourceTestCase, mock_env
 class TestEnvVarsSource(SourceTestCase):
     @mock_env('satella', '{"a":2}')
     def test_ok(self):
-        self.assertSourceHas(EnvVarsSource('satella'), {u"a": 2})
+        self.assertSourceHas(sources.EnvVarsSource('satella'), {u"a": 2})
 
     def test_none(self):
         self.assertSourceEmpty(OptionalSource(EnvVarsSource('satella')))
diff --git a/tests/test_posix/__init__.py b/tests/test_posix/__init__.py
index 6864cbaa7357da2b13bec13e6c643718243eeb51..b7f2695c358f5ac7e6e78fe2bbaea81cf9af735b 100644
--- a/tests/test_posix/__init__.py
+++ b/tests/test_posix/__init__.py
@@ -1,18 +1,26 @@
 # coding=UTF-8
 from __future__ import print_function, absolute_import, division
 
+import multiprocessing
 import os
 import sys
 import unittest
 
 from mock import patch, Mock
 
+from satella.posix import PIDFileLock, LockIsHeld
+
+
+def acquire_lock_file_and_wait_for_signal(q, p):
+    with PIDFileLock('lock', '.'):
+        p.put(None)
+        q.get()
+
 
 class TestPidlock(unittest.TestCase):
-    def test_pidlock(self):
-        from satella.posix.pidlock import AcquirePIDLock
 
-        with AcquirePIDLock('lock', '.', delete_on_dead=True):
+    def test_pidlock(self):
+        with PIDFileLock('lock', '.'):
             self.assertTrue(os.path.exists('./lock'))
             r = open('./lock', 'rb').read()
             try:
@@ -23,6 +31,19 @@ class TestPidlock(unittest.TestCase):
 
         self.assertTrue(not os.path.exists('./lock'))
 
+    def test_pidlock_multiacquire(self):
+        q, p = multiprocessing.Queue(), multiprocessing.Queue()
+        process = multiprocessing.Process(target=acquire_lock_file_and_wait_for_signal, args=(q, p))
+        process.start()
+        p.get()
+        n = PIDFileLock('lock', '.')
+        try:
+            self.assertRaises(LockIsHeld, lambda: n.acquire())
+        finally:
+            q.put(None)
+            process.terminate()
+            process.join()
+
 
 class TestDaemon(unittest.TestCase):
     @unittest.skipIf('win' in sys.platform, 'Running on Windows')