# Redis Hierarchical Distributed Read-Write Locking
This is a lightweight implementation of a hierarchical distributed read-write lock using Redis.
It supports concurrent readers and exclusive writers in a tree-like hierarchy, ensuring locks on ancestors affect descendants.
## Features
- Hierarchical locking with customizable path separators (e.g., `/`, `:`).
- Concurrent read locks on the same path or ancestors.
- Exclusive write locks on paths or descendants.
- Timeout and non-blocking lock options.
- Automatic lock refreshing for long-running operations.
## Installation
```bash
pip install redishilok
```
## Usage
```python
import asyncio
from redishilok import RedisHiLok
async def main():
hilok = RedisHiLok('redis://localhost:6379/0')
# Acquire a read lock
async with hilok.read('a/b'):
print("Read lock acquired on 'a/b'")
# Acquire a write lock with non-blocking mode
try:
async with hilok.write('a', block=False):
print("Write lock acquired on 'a'")
print("Never gets here")
except RuntimeError:
print("Failed to acquire write lock on 'a'")
asyncio.run(main())
```
## Examples
### Basic Hierarchical Locking
```python
import asyncio
from redishilok import RedisHiLok
async def main():
hilok = RedisHiLok('redis://localhost:6379/0')
# Concurrent readers
async with hilok.read('a'):
async with hilok.read('a/b/c'):
print("Both read locks succeed")
# Write lock blocks descendants
async with hilok.write('a'):
try:
async with hilok.read('a/b/c', timeout=0.1):
pass
except RuntimeError:
print("Failed to acquire read lock on 'a/b/c' due to write lock on 'a'")
asyncio.run(main())
```
### Custom Separator
```python
import asyncio
from redishilok import RedisHiLok
async def main():
hilok = RedisHiLok('redis://localhost', separator=':')
async with hilok.write('a:b:c'):
print("Write lock acquired on 'a:b:c'")
asyncio.run(main())
```
### Long-Running Operations with Refresh
```python
import asyncio
from redishilok import RedisHiLok
async def main():
hilok = RedisHiLok('redis://localhost', ttl=2000, refresh_interval=1000)
async with hilok.write('a/b'):
print("Long operation starts")
await asyncio.sleep(5) # Lock is automatically refreshed
asyncio.run(main())
```
### Manually-managed Operations with Handles
```python
import asyncio
from redishilok import RedisHiLok
async def main():
hilok = RedisHiLok('redis://localhost')
uuid = await hilok.acquire_write('a/b')
await asyncio.sleep(1) # Lock is not automatically refreshed, caller must "restore" the lock
# refresh by uuid, manually
manual = RedisHiLok('redis://localhost')
await manual.acquire_write("a/b", uuid=uuid)
# refresh automatically
other = RedisHiLok('redis://localhost', refresh_interval=1000)
async with other.write('a/b', uuid=uuid):
# lock is auto-refreshing in the background
pass
# free resources
await hilok.close()
await manual.close()
await other.close()
asyncio.run(main())
```
## Limitations
- Requires a running Redis instance.
- Not suitable for high-frequency locking scenarios (due to Redis round-trips).
- Lock fairness is not guaranteed (e.g., no queue for blocked writers).
## License
MIT
Raw data
{
"_id": null,
"home_page": "https://github.com/puzzlefin/redishilok",
"name": "redishilok",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": "redis, locking, distributed-locking, hierarchical-locking, python",
"author": "Erik Aronesty",
"author_email": "erik@q32.com",
"download_url": "https://files.pythonhosted.org/packages/8a/43/d0545d4b8d2befe773fb85e803bd190bbbc3d92071890dc0c47dfb8778cd/redishilok-1.1.0.tar.gz",
"platform": null,
"description": "# Redis Hierarchical Distributed Read-Write Locking\n\nThis is a lightweight implementation of a hierarchical distributed read-write lock using Redis.\n\nIt supports concurrent readers and exclusive writers in a tree-like hierarchy, ensuring locks on ancestors affect descendants.\n\n## Features\n- Hierarchical locking with customizable path separators (e.g., `/`, `:`).\n- Concurrent read locks on the same path or ancestors.\n- Exclusive write locks on paths or descendants.\n- Timeout and non-blocking lock options.\n- Automatic lock refreshing for long-running operations.\n\n## Installation\n\n```bash\npip install redishilok\n```\n\n## Usage\n\n```python\nimport asyncio\nfrom redishilok import RedisHiLok\n\nasync def main():\n hilok = RedisHiLok('redis://localhost:6379/0')\n\n # Acquire a read lock\n async with hilok.read('a/b'):\n print(\"Read lock acquired on 'a/b'\")\n\n # Acquire a write lock with non-blocking mode\n try:\n async with hilok.write('a', block=False):\n print(\"Write lock acquired on 'a'\")\n print(\"Never gets here\")\n except RuntimeError:\n print(\"Failed to acquire write lock on 'a'\")\n\nasyncio.run(main())\n```\n\n## Examples\n\n### Basic Hierarchical Locking\n\n```python\nimport asyncio\nfrom redishilok import RedisHiLok\n\nasync def main():\n hilok = RedisHiLok('redis://localhost:6379/0')\n\n # Concurrent readers\n async with hilok.read('a'):\n async with hilok.read('a/b/c'):\n print(\"Both read locks succeed\")\n\n # Write lock blocks descendants\n async with hilok.write('a'):\n try:\n async with hilok.read('a/b/c', timeout=0.1):\n pass\n except RuntimeError:\n print(\"Failed to acquire read lock on 'a/b/c' due to write lock on 'a'\")\n\nasyncio.run(main())\n```\n\n### Custom Separator\n\n```python\nimport asyncio\nfrom redishilok import RedisHiLok\n\nasync def main():\n hilok = RedisHiLok('redis://localhost', separator=':')\n async with hilok.write('a:b:c'):\n print(\"Write lock acquired on 'a:b:c'\")\n\nasyncio.run(main())\n```\n\n### Long-Running Operations with Refresh\n\n```python\nimport asyncio\nfrom redishilok import RedisHiLok\n\nasync def main():\n hilok = RedisHiLok('redis://localhost', ttl=2000, refresh_interval=1000)\n async with hilok.write('a/b'):\n print(\"Long operation starts\")\n await asyncio.sleep(5) # Lock is automatically refreshed\n\nasyncio.run(main())\n```\n\n\n\n### Manually-managed Operations with Handles\n\n```python\nimport asyncio\nfrom redishilok import RedisHiLok\n\nasync def main():\n hilok = RedisHiLok('redis://localhost')\n uuid = await hilok.acquire_write('a/b')\n await asyncio.sleep(1) # Lock is not automatically refreshed, caller must \"restore\" the lock\n\n # refresh by uuid, manually\n manual = RedisHiLok('redis://localhost')\n await manual.acquire_write(\"a/b\", uuid=uuid)\n\n # refresh automatically\n other = RedisHiLok('redis://localhost', refresh_interval=1000)\n async with other.write('a/b', uuid=uuid):\n # lock is auto-refreshing in the background\n pass\n\n # free resources\n await hilok.close()\n await manual.close()\n await other.close()\n\nasyncio.run(main())\n```\n\n## Limitations\n- Requires a running Redis instance.\n- Not suitable for high-frequency locking scenarios (due to Redis round-trips).\n- Lock fairness is not guaranteed (e.g., no queue for blocked writers).\n\n## License\nMIT\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Redis-based read-write distributed and hierarchical locking library",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/puzzlefin/redishilok",
"Repository": "https://github.com/puzzlefin/redishilok"
},
"split_keywords": [
"redis",
" locking",
" distributed-locking",
" hierarchical-locking",
" python"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d1b4e62943ef569f4ded3b7459e313abd738314beec4f73410722cffccedbe02",
"md5": "98ae5adc5439b8e060d6c9c6561b8260",
"sha256": "b5c507bbaa18c91f0f8836334052caca397ac48cb5940c0c2cd9ffcf2bb528d7"
},
"downloads": -1,
"filename": "redishilok-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "98ae5adc5439b8e060d6c9c6561b8260",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 7879,
"upload_time": "2024-11-26T01:59:24",
"upload_time_iso_8601": "2024-11-26T01:59:24.759038Z",
"url": "https://files.pythonhosted.org/packages/d1/b4/e62943ef569f4ded3b7459e313abd738314beec4f73410722cffccedbe02/redishilok-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "8a43d0545d4b8d2befe773fb85e803bd190bbbc3d92071890dc0c47dfb8778cd",
"md5": "9ae299783678cb976577f8da643d3d78",
"sha256": "60d761e36e7dff915c066776186109d296fb5d04d2abd38cfa0cd08e54885d8b"
},
"downloads": -1,
"filename": "redishilok-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "9ae299783678cb976577f8da643d3d78",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 7454,
"upload_time": "2024-11-26T01:59:26",
"upload_time_iso_8601": "2024-11-26T01:59:26.289919Z",
"url": "https://files.pythonhosted.org/packages/8a/43/d0545d4b8d2befe773fb85e803bd190bbbc3d92071890dc0c47dfb8778cd/redishilok-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-26 01:59:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "puzzlefin",
"github_project": "redishilok",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"circle": true,
"lcname": "redishilok"
}