python-redilock


Namepython-redilock JSON
Version 0.5.0 PyPI version JSON
download
home_pageNone
SummaryRedis Distributed Lock
upload_time2024-09-29 18:45:15
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT License
keywords redis lock mutex distributed
VCS
bugtrack_url
requirements redis
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # redilock :  Redis Distributed Lock

### Introduction - what is a Lock / Mutex?

In multithreaded/asynchronous programs, multiple "tasks" run in parallel.
One challenge with such parallel tasks is that sometimes there is a need to make sure that only one task will call a
function or access a resource.

A lock (a.k.a Mutex) is a code facility that acts as a gatekeeper and allows only one task to access a resource.

Python provides `threading.Lock()` and `asyncio.Lock()` exactly for this purpose.

### Distributed Locks

When working with multiple processes or multiple services/hosts - we also need a lock but now we need a “distributed
lock” which is similar to the standard lock except that it is available to other programs/services/hosts.

As Redis is a storage/caching system, we can use it to act as a distributed lock.

**Redilock** is a simple python package that acts as a simple distributed lock and allows you to add locking capabilites
to any cloud/distributed environment.

**Redilock** main features:

* Simple to use - either use `with-statement` (context manager) or directly call the `lock()` and `unlock()` methods.
* Supports both synchronous implementation and an async implementation
* Safe:
    * Caller must specify the lock-expiration (TTL - time to lock) so even if the program/host crashes - the lock will
      be eventually released
    * Unlocking can be performed only by the task who put the lock

### Installation
```# pip install python-redilock```
  
### Usage & Examples

_(for synchronous code, async is identical and straightforward. check out the examples directory for more examples)_:

The easiest way to use a lock (whether async or not) is using the `with statement`

```
import redilock.sync_redilock as redilock
mylock = redilock.DistributedLock(ttl=30)  # auto-release after 30s

with mylock("my_lock"):
  print("I've got the lock !!")
```

Once creating the `DistributedLock` object (`mylock` variable)  it can be used to lock different resources (or different locks, if you prefer).
In the example above, we use a simple with-statement to lock the "my_lock" lock,  print something and unlock.
When using this approach, the lock is always blocking - the code will wait until the lock is available.
Note that we're using `ttl=30` which means that if our code fails or the program crashes - the lock will expire after 30 seconds.


If better control over the lock is needed - you can directly use the  `lock` and `unlock` methods:

```
import redilock.sync_redilock as redilock

lock = redilock.DistributedLock(ttl=300)  # lock for maximum 5min

unlock_secret_token = lock.lock("my_lock")  # Acquire the lock
print("I've got the lock !!")
lock.unlock(unlock_secret_token)  # Release the lock
```

By default, if you try to acquire a lock - your program will be blocked until the lock is acquired.
you can specify non-blocking mode which can be useful in many cases, for example:

```
import redilock.sync_redilock as redilock

lock = redilock.DistributedLock(ttl=10)  # lock for 10s

lock.lock("my_lock")  
if not lock.lock("my_lock", block=False):  # try to lock again but do't block  
  print("Couldnt acquire the lock")
```

Note that in the example above we lock for 10s and then we try to lock without blocking .
If you run the example twice - the second time will have to wait ~10s until the lock (from the first run) is released.

### Good to know and best practices
* The TTL is super important. it dictates when to auto-release the lock if your code doesnt release it
  (in case of a bug or a crash). You should not rely on it for unlocking as your code should either unlock
  using the `unlock` function or via `with statement`.
  As so, a large value (e.g 30-60 seconds) is probably fine as it will be used only in extreme cases.
* you can specify TTL when instantiating the class or when performing the lock operation itself.  
* When using blocking lock there is a background loop that checks redis periodically if the lock is still acquired.
  The system uses check-interval of 0.25. You can modify this value if needed via the `interval` parameter.
```
mylock = redilock.DistributedLock(interval=2)
```
  
* The lock is not re-entrant. it means that if a task (thread/coroutine) owns it and tries to lock again - it will be blocked until the lock expires (ttl). 
For example
```
with mylock("my_lock", ttl=5):
  print("I've got the lock, let's lock again")
  with mylock("my_lock", ttl=5):  # <------------- will block for 5s
    print("I've got the lock again")
```
Technically, it is possible to create a re-entrant distributed lock but i tend to believe
that if you need such facility - you're probably using the wrong architecture or you don't need this redilock :) .

* using a `with-statement` for locking is indeed the easiest way however there is one big tricky "gotcha" with this approach.
if your TTL is too short  - the lock will expire while you're still in the "with"
Consider the following code:
```
import time
import redilock.sync_redilock as redilock

mylock = redilock.DistributedLock(ttl=2)  # lock that will autoexpire after 2s

with mylock("my_lock"):
    print("I've got the lock !!")
    time.sleep(3)
    print("Hmm...i dont have the lock anymore :( ")
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "python-redilock",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "redis, lock, mutex, distributed",
    "author": null,
    "author_email": "Zvika Ferentz <zvika.ferentz@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/60/81/81f5f7dd035e7a033762abf33cc3dfe688a3cdf5f04b8e97b43edd560f08/python_redilock-0.5.0.tar.gz",
    "platform": null,
    "description": "# redilock :  Redis Distributed Lock\n\n### Introduction - what is a Lock / Mutex?\n\nIn multithreaded/asynchronous programs, multiple \"tasks\" run in parallel.\nOne challenge with such parallel tasks is that sometimes there is a need to make sure that only one task will call a\nfunction or access a resource.\n\nA lock (a.k.a Mutex) is a code facility that acts as a gatekeeper and allows only one task to access a resource.\n\nPython provides `threading.Lock()` and `asyncio.Lock()` exactly for this purpose.\n\n### Distributed Locks\n\nWhen working with multiple processes or multiple services/hosts - we also need a lock but now we need a \u201cdistributed\nlock\u201d which is similar to the standard lock except that it is available to other programs/services/hosts.\n\nAs Redis is a storage/caching system, we can use it to act as a distributed lock.\n\n**Redilock** is a simple python package that acts as a simple distributed lock and allows you to add locking capabilites\nto any cloud/distributed environment.\n\n**Redilock** main features:\n\n* Simple to use - either use `with-statement` (context manager) or directly call the `lock()` and `unlock()` methods.\n* Supports both synchronous implementation and an async implementation\n* Safe:\n    * Caller must specify the lock-expiration (TTL - time to lock) so even if the program/host crashes - the lock will\n      be eventually released\n    * Unlocking can be performed only by the task who put the lock\n\n### Installation\n```# pip install python-redilock```\n  \n### Usage & Examples\n\n_(for synchronous code, async is identical and straightforward. check out the examples directory for more examples)_:\n\nThe easiest way to use a lock (whether async or not) is using the `with statement`\n\n```\nimport redilock.sync_redilock as redilock\nmylock = redilock.DistributedLock(ttl=30)  # auto-release after 30s\n\nwith mylock(\"my_lock\"):\n  print(\"I've got the lock !!\")\n```\n\nOnce creating the `DistributedLock` object (`mylock` variable)  it can be used to lock different resources (or different locks, if you prefer).\nIn the example above, we use a simple with-statement to lock the \"my_lock\" lock,  print something and unlock.\nWhen using this approach, the lock is always blocking - the code will wait until the lock is available.\nNote that we're using `ttl=30` which means that if our code fails or the program crashes - the lock will expire after 30 seconds.\n\n\nIf better control over the lock is needed - you can directly use the  `lock` and `unlock` methods:\n\n```\nimport redilock.sync_redilock as redilock\n\nlock = redilock.DistributedLock(ttl=300)  # lock for maximum 5min\n\nunlock_secret_token = lock.lock(\"my_lock\")  # Acquire the lock\nprint(\"I've got the lock !!\")\nlock.unlock(unlock_secret_token)  # Release the lock\n```\n\nBy default, if you try to acquire a lock - your program will be blocked until the lock is acquired.\nyou can specify non-blocking mode which can be useful in many cases, for example:\n\n```\nimport redilock.sync_redilock as redilock\n\nlock = redilock.DistributedLock(ttl=10)  # lock for 10s\n\nlock.lock(\"my_lock\")  \nif not lock.lock(\"my_lock\", block=False):  # try to lock again but do't block  \n  print(\"Couldnt acquire the lock\")\n```\n\nNote that in the example above we lock for 10s and then we try to lock without blocking .\nIf you run the example twice - the second time will have to wait ~10s until the lock (from the first run) is released.\n\n### Good to know and best practices\n* The TTL is super important. it dictates when to auto-release the lock if your code doesnt release it\n  (in case of a bug or a crash). You should not rely on it for unlocking as your code should either unlock\n  using the `unlock` function or via `with statement`.\n  As so, a large value (e.g 30-60 seconds) is probably fine as it will be used only in extreme cases.\n* you can specify TTL when instantiating the class or when performing the lock operation itself.  \n* When using blocking lock there is a background loop that checks redis periodically if the lock is still acquired.\n  The system uses check-interval of 0.25. You can modify this value if needed via the `interval` parameter.\n```\nmylock = redilock.DistributedLock(interval=2)\n```\n  \n* The lock is not re-entrant. it means that if a task (thread/coroutine) owns it and tries to lock again - it will be blocked until the lock expires (ttl). \nFor example\n```\nwith mylock(\"my_lock\", ttl=5):\n  print(\"I've got the lock, let's lock again\")\n  with mylock(\"my_lock\", ttl=5):  # <------------- will block for 5s\n    print(\"I've got the lock again\")\n```\nTechnically, it is possible to create a re-entrant distributed lock but i tend to believe\nthat if you need such facility - you're probably using the wrong architecture or you don't need this redilock :) .\n\n* using a `with-statement` for locking is indeed the easiest way however there is one big tricky \"gotcha\" with this approach.\nif your TTL is too short  - the lock will expire while you're still in the \"with\"\nConsider the following code:\n```\nimport time\nimport redilock.sync_redilock as redilock\n\nmylock = redilock.DistributedLock(ttl=2)  # lock that will autoexpire after 2s\n\nwith mylock(\"my_lock\"):\n    print(\"I've got the lock !!\")\n    time.sleep(3)\n    print(\"Hmm...i dont have the lock anymore :( \")\n```\n",
    "bugtrack_url": null,
    "license": "MIT License",
    "summary": "Redis Distributed Lock",
    "version": "0.5.0",
    "project_urls": {
        "Homepage": "https://github.com/zferentz/python-redilock",
        "Issues": "https://github.com/zferentz/python-redilock/issues"
    },
    "split_keywords": [
        "redis",
        " lock",
        " mutex",
        " distributed"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "60353c96436bf419987dbaea94f19aeac1b8f8e9e516ee4a901d6e98b003fdf1",
                "md5": "fca4f9a497719a8aed20a038e7b5c763",
                "sha256": "f8186f1b0f551055b5989441c3fa5e7cecbfbfa1cd538977345c1bfa1b32260a"
            },
            "downloads": -1,
            "filename": "python_redilock-0.5.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fca4f9a497719a8aed20a038e7b5c763",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 9121,
            "upload_time": "2024-09-29T18:45:14",
            "upload_time_iso_8601": "2024-09-29T18:45:14.240248Z",
            "url": "https://files.pythonhosted.org/packages/60/35/3c96436bf419987dbaea94f19aeac1b8f8e9e516ee4a901d6e98b003fdf1/python_redilock-0.5.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "608181f5f7dd035e7a033762abf33cc3dfe688a3cdf5f04b8e97b43edd560f08",
                "md5": "14dd7dba2c3173916e0c858a7c6135f9",
                "sha256": "5e18ef599a744bc4d6d7dd2083907e4e6b6c4ef14b0f13da5b725e1d2112b635"
            },
            "downloads": -1,
            "filename": "python_redilock-0.5.0.tar.gz",
            "has_sig": false,
            "md5_digest": "14dd7dba2c3173916e0c858a7c6135f9",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 9140,
            "upload_time": "2024-09-29T18:45:15",
            "upload_time_iso_8601": "2024-09-29T18:45:15.511387Z",
            "url": "https://files.pythonhosted.org/packages/60/81/81f5f7dd035e7a033762abf33cc3dfe688a3cdf5f04b8e97b43edd560f08/python_redilock-0.5.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-09-29 18:45:15",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "zferentz",
    "github_project": "python-redilock",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "redis",
            "specs": [
                [
                    ">=",
                    "4.3.1"
                ]
            ]
        }
    ],
    "lcname": "python-redilock"
}
        
Elapsed time: 2.15768s