Skip to main content

When there are not enough locks from the standard library

Project description

logo

Downloads Downloads codecov Lines of code Hits-of-Code Test-Package Python versions PyPI version Checked with mypy Ruff

It contains several useful additions to the standard thread synchronization tools, such as lock protocols and locks with advanced functionality.

Table of contents

Installation

Get the locklib from the pypi:

pip install locklib

... or directly from git:

pip install git+https://github.com/pomponchik/locklib.git

You can also quickly try out this and other packages without having to install using instld.

Lock protocols

Protocols are needed so that you can write typed code without being bound to specific classes. Protocols from this library allow you to "equalize" locks from the standard library and third-party locks, including those provided by this library.

We consider the basic characteristic of the lock protocol to be the presence of two methods for an object:

def acquire() -> None: pass
def release() -> None: pass

All the locks from the standard library correspond to this, as well as the locks presented in this one.

To check for compliance with this minimum standard, locklib contains the LockProtocol. You can check for yourself that all the locks match it:

from multiprocessing import Lock as MLock
from threading import Lock as TLock, RLock as TRLock
from asyncio import Lock as ALock

from locklib import SmartLock, LockProtocol

print(isinstance(MLock(), LockProtocol)) # True
print(isinstance(TLock(), LockProtocol)) # True
print(isinstance(TRLock(), LockProtocol)) # True
print(isinstance(ALock(), LockProtocol)) # True
print(isinstance(SmartLock(), LockProtocol)) # True

However! Most idiomatic python code using locks uses them as context managers. If your code is like that too, you can use one of the two inheritors of the regular LockProtocol: ContextLockProtocol or AsyncContextLockProtocol. Thus, the protocol inheritance hierarchy looks like this:

LockProtocol
 ├── ContextLockProtocol
 └── AsyncContextLockProtocol

ContextLockProtocol describes the objects described by LockProtocol, which are also context managers. AsyncContextLockProtocol, by analogy, describes objects that are instances of LockProtocol, as well as asynchronous context managers.

Almost all the locks from the standard library are instances of ContextLockProtocol, as well as SmartLock.

from multiprocessing import Lock as MLock
from threading import Lock as TLock, RLock as TRLock

from locklib import SmartLock, ContextLockProtocol

print(isinstance(MLock(), ContextLockProtocol)) # True
print(isinstance(TLock(), ContextLockProtocol)) # True
print(isinstance(TRLock(), ContextLockProtocol)) # True
print(isinstance(SmartLock(), ContextLockProtocol)) # True

However, the Lock from asyncio belongs to a separate category and AsyncContextLockProtocol is needed to describe it:

from asyncio import Lock
from locklib import AsyncContextLockProtocol

print(isinstance(Lock(), AsyncContextLockProtocol)) # True

If you use type hints and static verification tools like mypy, we highly recommend using the narrowest of the presented categories for lock protocols, which describe the requirements for your locales.

SmartLock - deadlock is impossible with it

locklib contains a lock that cannot get into the deadlock - SmartLock, based on Wait-for Graph. You can use it as a usual Lock from the standard library. Let's check that it can protect us from the race condition in the same way:

from threading import Thread
from locklib import SmartLock

lock = SmartLock()
counter = 0

def function():
  global counter

  for _ in range(1000):
      with lock:
          counter += 1

thread_1 = Thread(target=function)
thread_2 = Thread(target=function)
thread_1.start()
thread_2.start()

assert counter == 2000

Yeah, in this case the lock helps us not to get a race condition, as the standard Lock does. But! Let's trigger a deadlock and look what happens:

from threading import Thread
from locklib import SmartLock

lock_1 = SmartLock()
lock_2 = SmartLock()

def function_1():
  while True:
    with lock_1:
      with lock_2:
        pass

def function_2():
  while True:
    with lock_2:
      with lock_1:
        pass

thread_1 = Thread(target=function_1)
thread_2 = Thread(target=function_2)
thread_1.start()
thread_2.start()

And... We have an exception like this:

...
locklib.errors.DeadLockError: A cycle between 1970256th and 1970257th threads has been detected.

Deadlocks are impossible for this lock!

If you want to catch the exception, import this from the locklib too:

from locklib import DeadLockError

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

locklib-0.0.17.tar.gz (8.9 kB view details)

Uploaded Source

Built Distribution

locklib-0.0.17-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

File details

Details for the file locklib-0.0.17.tar.gz.

File metadata

  • Download URL: locklib-0.0.17.tar.gz
  • Upload date:
  • Size: 8.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for locklib-0.0.17.tar.gz
Algorithm Hash digest
SHA256 4c84c3c3f620fe3491bb0de2333859145f36f969fe9ce8c721b291ac99714283
MD5 11acfc7bbf4324268b569105e88ce9d0
BLAKE2b-256 3ba3a35163dce64b6ccfa10dec51dce8b27f573a5b903a14b5f8078a0f79b61d

See more details on using hashes here.

Provenance

The following attestation bundles were made for locklib-0.0.17.tar.gz:

Publisher: release.yml on pomponchik/locklib

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file locklib-0.0.17-py3-none-any.whl.

File metadata

  • Download URL: locklib-0.0.17-py3-none-any.whl
  • Upload date:
  • Size: 8.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for locklib-0.0.17-py3-none-any.whl
Algorithm Hash digest
SHA256 f9dfdb1bf270d7bf83133164318bb7cb2043aa663999eab54152023573c17c41
MD5 8395d3e881c9fa51655b8f9095e68e17
BLAKE2b-256 c9fbc77d3dc0b864ec97cd552f49e9fd51c3fc3bc443cf964105c0b122010a59

See more details on using hashes here.

Provenance

The following attestation bundles were made for locklib-0.0.17-py3-none-any.whl:

Publisher: release.yml on pomponchik/locklib

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page