{ "info": { "author": "Randy Syring", "author_email": "randy.syring@level12.io", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": ".. default-role:: code\n\nPostgreSQL Advisory Locks (PALs)\n################################\n\n.. image:: https://circleci.com/gh/level12/pals.svg?style=shield\n :target: https://circleci.com/gh/level12/pals\n.. image:: https://codecov.io/gh/level12/pals/branch/master/graph/badge.svg\n :target: https://codecov.io/gh/level12/pals\n\n\nIntroduction\n============\n\nPALs makes it easy to use `PostgreSQL Advisory Locks`_ to do distributed application level\nlocking.\n\nDo not confuse this type of locking with table or row locking in PostgreSQL. It's not the same\nthing.\n\nDistributed application level locking can be implemented by using Redis, Memcache, ZeroMQ and\nothers. But for those who are already using PostgreSQL, setup & management of another service is\nunnecessary.\n\n.. _PostgreSQL Advisory Locks: https://www.postgresql.org/docs/current/static/explicit-locking.html\n\n\nUsage\n========\n\n.. code:: python\n\n # Think of the Locker instance as a Lock factory.\n locker = pals.Locker('my-app-name', 'postgresql://user:pass@server/dbname')\n\n lock1 = locker.lock('my-lock')\n lock2 = locker.lock('my-lock')\n\n # The first acquire works\n assert lock1.acquire() is True\n\n # Non blocking version should fail immediately\n assert lock2.acquire(blocking=False) is False\n\n # Blocking version will retry and eventually fail\n acquired, retries = lock2.acquire(return_retries=True)\n assert acquired is False\n assert retries > 4\n\n # You can set the retry parameters yourself if you don't like our defaults.\n lock2.acquire(retry_delay=100, retry_timeout=300)\n\n # They can also be set on the lock instance\n lock3 = locker.lock('my-lock', retry_delay=100, retry_timeout=300)\n\n # Release the lock\n lock1.release()\n\n # Recommended usage pattern:\n if not lock1.acquire():\n # Remember to check to make sure you got your lock\n return\n try:\n # do my work here\n finally:\n lock1.release()\n\n # But more recommended and easier is to use the lock as a context manager:\n with lock1:\n assert lock2.acquire() is False\n\n # Outside the context manager the lock should have been released and we can get it now\n assert lock2.acquire()\n\n # The context manager version will throw an exception if it fails to acquire the lock. This\n # pattern was chosen because it feels symantically wrong to have to check to see if the lock\n # was actually acquired inside the context manager. If the code inside is ran, the lock was\n # acquired.\n try:\n with lock1:\n # We won't get here because lock2 acquires the lock just above\n pass\n except pals.AcquireFailure:\n pass\n\n\nRunning Tests Locally\n=====================\n\nSetup Database Connection\n-------------------------\n\nWe have provided a docker-compose file, but you don't have to use it::\n\n $ docker-compose up -d\n $ export PALS_DB_URL=postgresql://postgres:password@localhost:54321/postgres\n\nYou can also put the environment variable in a .env file and pipenv will pick it up.\n\nRun the Tests\n-------------\n\nWith tox::\n\n $ tox\n\nOr, manually::\n\n $ pipenv install --dev\n $ pipenv shell\n $ pytest pals/tests.py\n\n\nLock Releasing & Expiration\n---------------------------\n\nUnlike locking systems built on cache services like Memcache and Redis, whose keys can be expired\nby the service, there is no faculty for expiring an advisory lock in PostgreSQL. If a client\nholds a lock and then sleeps/hangs for mins/hours/days, no other client will be able to get that\nlock until the client releases it. This actually seems like a good thing to us, if a lock is\nacquired, it should be kept until released.\n\nBut what about accidental failures to release the lock?\n\n1. If a developer uses `lock.acquire()` but doesn't later call `lock.release()`?\n2. If code inside a lock accidentally throws an exception (and .release() is not called)?\n3. If the process running the application crashes or the process' server dies?\n\nPALs helps #1 and #2 above in a few different ways:\n\n* Locks work as context managers. Use them as much as possible to guarantee a lock is released.\n* Locks release their lock when garbage collected.\n* PALs uses a dedicated SQLAlchemy connection pool. When a connection is returned to the pool,\n either because a connection `.close()` is called or due to garbage collection of the connection,\n PALs issues a `pg_advisory_unlock_all()`. It should therefore be impossible for an idle\n connection in the pool to ever still be holding a lock.\n\nRegarding #3 above, `pg_advisory_unlock_all()` is implicitly invoked by PostgreSQL whenever a\nconnection (a.k.a session) ends, even if the client disconnects ungracefully. So if a process\ncrashes or otherwise disappears, PostgreSQL should notice and remove all locks held by that\nconnection/session.\n\nThe possibility could exist that PostgreSQL does not detect a connection has closed and keeps\na lock open indefinitely. However, in manual testing using `scripts/hang.py` no way was found\nto end the Python process without PostgreSQL detecting it.\n\n\nSee Also\n==========\n\n* https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/\n* https://github.com/binded/advisory-lock\n* https://github.com/vaidik/sherlock\n* https://github.com/Xof/django-pglocks\n\n\n\nChangelog\n=========\n\n0.2.0 released 2019-03-07\n-------------------------\n\n- Fix misspelling of \"acquire\" (737763f_)\n\n.. _737763f: https://github.com/level12/pals/commit/737763f\n\n\n0.1.0 released 2019-02-22\n-------------------------\n\n- Use `lock_timeout` setting to expire blocking calls (d0216ce_)\n- fix tox (1b0ffe2_)\n- rename to PALs (95d5a3c_)\n- improve readme (e8dd6f2_)\n- move tests file to better location (a153af5_)\n- add flake8 dep (3909c95_)\n- fix tests so they work locally too (7102294_)\n- get circleci working (28f16d2_)\n- suppress exceptions in Lock __del__ (e29c1ce_)\n- Add hang.py script (3372ef0_)\n- fix packaging stuff, update readme (cebd976_)\n- initial commit (871b877_)\n\n.. _d0216ce: https://github.com/level12/pals/commit/d0216ce\n.. _1b0ffe2: https://github.com/level12/pals/commit/1b0ffe2\n.. _95d5a3c: https://github.com/level12/pals/commit/95d5a3c\n.. _e8dd6f2: https://github.com/level12/pals/commit/e8dd6f2\n.. _a153af5: https://github.com/level12/pals/commit/a153af5\n.. _3909c95: https://github.com/level12/pals/commit/3909c95\n.. _7102294: https://github.com/level12/pals/commit/7102294\n.. _28f16d2: https://github.com/level12/pals/commit/28f16d2\n.. _e29c1ce: https://github.com/level12/pals/commit/e29c1ce\n.. _3372ef0: https://github.com/level12/pals/commit/3372ef0\n.. _cebd976: https://github.com/level12/pals/commit/cebd976\n.. _871b877: https://github.com/level12/pals/commit/871b877\n\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/level12/pals", "keywords": "", "license": "BSD", "maintainer": "", "maintainer_email": "", "name": "PALs", "package_url": "https://pypi.org/project/PALs/", "platform": "", "project_url": "https://pypi.org/project/PALs/", "project_urls": { "Homepage": "https://github.com/level12/pals" }, "release_url": "https://pypi.org/project/PALs/0.2.0/", "requires_dist": [ "sqlalchemy" ], "requires_python": "", "summary": "Easy distributed locking using PostgreSQL Advisory Locks.", "version": "0.2.0" }, "last_serial": 4912655, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "cf02854625bba2a569ff21950b3a13a5", "sha256": "44ce46c89493ed7a24028404c582f858d64b9600a2486f7ce32c567ef3eb2c70" }, "downloads": -1, "filename": "PALs-0.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "cf02854625bba2a569ff21950b3a13a5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7444, "upload_time": "2019-02-23T02:20:02", "url": "https://files.pythonhosted.org/packages/22/fe/453fd22dbe1d007b264a78c6d6b53a38804636001b7a133b6af7d6763804/PALs-0.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1b4c8e4982d5e34c87e515212bfeba06", "sha256": "a58e858cef53fe73af75ad1d35638cebf2eaa0b049da48eee53a187ae387a39e" }, "downloads": -1, "filename": "PALs-0.1.0.tar.gz", "has_sig": false, "md5_digest": "1b4c8e4982d5e34c87e515212bfeba06", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18458, "upload_time": "2019-02-23T02:20:05", "url": "https://files.pythonhosted.org/packages/11/86/305fdc9a467e934b8ed8d4801ac284d84249e4e74a9e93ae6dae5b2bf25f/PALs-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "5a6a88454f00d8cb9566458d26c455db", "sha256": "af354f8f591dd5dad6afef4d84de78b9b285376f211474c85411cce3fd2e2568" }, "downloads": -1, "filename": "PALs-0.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "5a6a88454f00d8cb9566458d26c455db", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7495, "upload_time": "2019-03-07T21:42:33", "url": "https://files.pythonhosted.org/packages/cc/28/e79f5de89d0187dab9aa2c1fece0b7a89f79f1f145ffb16a249ca0b9fcf7/PALs-0.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e568ca87ce3d845be9baad0ad5c42b0b", "sha256": "7382180c632665fe02b1a773467547116fc172dfa3e1236503bab60e447c6000" }, "downloads": -1, "filename": "PALs-0.2.0.tar.gz", "has_sig": false, "md5_digest": "e568ca87ce3d845be9baad0ad5c42b0b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18538, "upload_time": "2019-03-07T21:42:35", "url": "https://files.pythonhosted.org/packages/6b/c3/1e2243e4752f1334e240d0ee5458591b534219f43398d5586f421d6fc1d5/PALs-0.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "5a6a88454f00d8cb9566458d26c455db", "sha256": "af354f8f591dd5dad6afef4d84de78b9b285376f211474c85411cce3fd2e2568" }, "downloads": -1, "filename": "PALs-0.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "5a6a88454f00d8cb9566458d26c455db", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7495, "upload_time": "2019-03-07T21:42:33", "url": "https://files.pythonhosted.org/packages/cc/28/e79f5de89d0187dab9aa2c1fece0b7a89f79f1f145ffb16a249ca0b9fcf7/PALs-0.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e568ca87ce3d845be9baad0ad5c42b0b", "sha256": "7382180c632665fe02b1a773467547116fc172dfa3e1236503bab60e447c6000" }, "downloads": -1, "filename": "PALs-0.2.0.tar.gz", "has_sig": false, "md5_digest": "e568ca87ce3d845be9baad0ad5c42b0b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18538, "upload_time": "2019-03-07T21:42:35", "url": "https://files.pythonhosted.org/packages/6b/c3/1e2243e4752f1334e240d0ee5458591b534219f43398d5586f421d6fc1d5/PALs-0.2.0.tar.gz" } ] }