././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1758180751.180958 zc_lockfile-4.0/0000755000076600000240000000000015062732617013355 5ustar00m.howitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/.pre-commit-config.yaml0000644000076600000240000000132615062732616017637 0ustar00m.howitzstaff# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python minimum_pre_commit_version: '3.6' repos: - repo: https://github.com/pycqa/isort rev: "6.0.1" hooks: - id: isort - repo: https://github.com/hhatto/autopep8 rev: "v2.3.2" hooks: - id: autopep8 args: [--in-place, --aggressive, --aggressive] - repo: https://github.com/asottile/pyupgrade rev: v3.20.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/isidentical/teyit rev: 0.4.3 hooks: - id: teyit - repo: https://github.com/PyCQA/flake8 rev: "7.3.0" hooks: - id: flake8 additional_dependencies: - flake8-debugger == 4.1.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/CHANGES.rst0000644000076600000240000000470215062732616015161 0ustar00m.howitzstaffChange History *************** 4.0 (2025-09-18) ================ - Replace ``pkg_resources`` namespace with PEP 420 native namespace. - Add support for Python 3.12, 3.13. - Drop support for Python 3.7, 3.8. 3.0.post1 (2023-02-28) ====================== - Add ``python_requires`` to ``setup.py`` to prevent installing on not supported old Python versions. 3.0 (2023-02-23) ================ - Add support for Python 3.9, 3.10, 3.11. - Drop support for Python 2.7, 3.5, 3.6. - Drop support for deprecated ``python setup.py test``. 2.0 (2019-08-08) ================ - Extracted new ``SimpleLockFile`` that removes implicit behavior writing to the lock file, and instead allows a subclass to define that behavior. (`#15 `_) - ``SimpleLockFile`` and thus ``LockFile`` are now new-style classes. Any clients relying on ``LockFile`` being an old-style class will need to be adapted. - Drop support for Python 3.4. - Add support for Python 3.8b3. 1.4 (2018-11-12) ================ - Claim support for Python 3.6 and 3.7. - Drop Python 2.6 and 3.3. 1.3.0 (2018-04-23) ================== - Stop logging failure to acquire locks. Clients can do that if they wish. - Claim support for Python 3.4 and 3.5. - Drop Python 3.2 support because pip no longer supports it. 1.2.1 (2016-06-19) ================== - Fixed: unlocking and locking didn't work when a multiprocessing process was running (and presumably other conditions). 1.2.0 (2016-06-09) ================== - Added the ability to include the hostname in the lock file content. - Code and ReST markup cosmetics. [alecghica] 1.1.0 (2013-02-12) ================== - Added Trove classifiers and made setup.py zest.releaser friendly. - Added Python 3.2, 3.3 and PyPy 1.9 support. - Removed Python 2.4 and Python 2.5 support. 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== - Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/CONTRIBUTING.md0000644000076600000240000000144315062732616015607 0ustar00m.howitzstaff # Contributing to zopefoundation projects The projects under the zopefoundation GitHub organization are open source and welcome contributions in different forms: * bug reports * code improvements and bug fixes * documentation improvements * pull request reviews For any changes in the repository besides trivial typo fixes you are required to sign the contributor agreement. See https://www.zope.dev/developer/becoming-a-committer.html for details. Please visit our [Developer Guidelines](https://www.zope.dev/developer/guidelines.html) if you'd like to contribute code changes and our [guidelines for reporting bugs](https://www.zope.dev/developer/reporting-bugs.html) if you want to file a bug report. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/COPYRIGHT.txt0000644000076600000240000000004015062732616015457 0ustar00m.howitzstaffZope Foundation and Contributors././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/LICENSE.txt0000644000076600000240000000402615062732616015201 0ustar00m.howitzstaffZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/MANIFEST.in0000644000076600000240000000040015062732616015104 0ustar00m.howitzstaff# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python include *.md include *.rst include *.txt include buildout.cfg include tox.ini include .pre-commit-config.yaml recursive-include src *.py recursive-include src *.txt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1808753 zc_lockfile-4.0/PKG-INFO0000644000076600000240000001472715062732617014465 0ustar00m.howitzstaffMetadata-Version: 2.4 Name: zc.lockfile Version: 4.0 Summary: Basic inter-process locks Home-page: https://github.com/zopefoundation/zc.lockfile Author: Zope Foundation Author-email: zope-dev@zope.dev License: ZPL-2.1 Keywords: lock Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Requires-Python: >=3.9 License-File: LICENSE.txt Requires-Dist: setuptools Provides-Extra: test Requires-Dist: zope.testing; extra == "test" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: provides-extra Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print("Can't lock file") Can't lock file .. We don't log failure to acquire. >>> for record in handler.records: # doctest: +ELLIPSIS ... print(record.levelname+' '+record.getMessage()) To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Hostname in lock file ===================== In a container environment (e.g. Docker), the PID is typically always identical even if multiple containers are running under the same operating system instance. Clearly, inspecting lock files doesn't then help much in debugging. To identify the container which created the lock file, we need information about the container in the lock file. Since Docker uses the container identifier or name as the hostname, this information can be stored in the lock file in addition to or instead of the PID. Use the ``content_template`` keyword argument to ``LockFile`` to specify a custom lock file content format: >>> lock = zc.lockfile.LockFile('lock', content_template='{pid};{hostname}') >>> lock.close() If you now inspected the lock file, you would see e.g.: $ cat lock 123;myhostname Change History *************** 4.0 (2025-09-18) ================ - Replace ``pkg_resources`` namespace with PEP 420 native namespace. - Add support for Python 3.12, 3.13. - Drop support for Python 3.7, 3.8. 3.0.post1 (2023-02-28) ====================== - Add ``python_requires`` to ``setup.py`` to prevent installing on not supported old Python versions. 3.0 (2023-02-23) ================ - Add support for Python 3.9, 3.10, 3.11. - Drop support for Python 2.7, 3.5, 3.6. - Drop support for deprecated ``python setup.py test``. 2.0 (2019-08-08) ================ - Extracted new ``SimpleLockFile`` that removes implicit behavior writing to the lock file, and instead allows a subclass to define that behavior. (`#15 `_) - ``SimpleLockFile`` and thus ``LockFile`` are now new-style classes. Any clients relying on ``LockFile`` being an old-style class will need to be adapted. - Drop support for Python 3.4. - Add support for Python 3.8b3. 1.4 (2018-11-12) ================ - Claim support for Python 3.6 and 3.7. - Drop Python 2.6 and 3.3. 1.3.0 (2018-04-23) ================== - Stop logging failure to acquire locks. Clients can do that if they wish. - Claim support for Python 3.4 and 3.5. - Drop Python 3.2 support because pip no longer supports it. 1.2.1 (2016-06-19) ================== - Fixed: unlocking and locking didn't work when a multiprocessing process was running (and presumably other conditions). 1.2.0 (2016-06-09) ================== - Added the ability to include the hostname in the lock file content. - Code and ReST markup cosmetics. [alecghica] 1.1.0 (2013-02-12) ================== - Added Trove classifiers and made setup.py zest.releaser friendly. - Added Python 3.2, 3.3 and PyPy 1.9 support. - Removed Python 2.4 and Python 2.5 support. 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== - Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/README.rst0000644000076600000240000000110715062732616015042 0ustar00m.howitzstaff************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/pyproject.toml0000644000076600000240000000120115062732616016262 0ustar00m.howitzstaff# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [build-system] requires = [ "setuptools == 78.1.1", "wheel", ] build-backend = "setuptools.build_meta" [tool.coverage.run] branch = true source = ["zc.lockfile"] [tool.coverage.report] fail_under = 100 precision = 2 ignore_errors = true show_missing = true exclude_lines = [ "pragma: no cover", "pragma: nocover", "except ImportError:", "raise NotImplementedError", "if __name__ == '__main__':", "self.fail", "raise AssertionError", "raise unittest.Skip", ] [tool.coverage.html] directory = "parts/htmlcov" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1811626 zc_lockfile-4.0/setup.cfg0000644000076600000240000000060515062732617015177 0ustar00m.howitzstaff[flake8] doctests = 1 [check-manifest] ignore = .editorconfig .meta.toml [isort] force_single_line = True combine_as_imports = True sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER known_third_party = docutils, pkg_resources, pytz known_zope = known_first_party = default_section = ZOPE line_length = 79 lines_after_imports = 2 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/setup.py0000644000076600000240000000443715062732616015076 0ustar00m.howitzstaff############################################################################## # # Copyright (c) Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os from setuptools import setup version = '4.0' def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() long_description = ( read('README.rst') + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n' + read('src', 'zc', 'lockfile', 'README.txt') + '\n' + read('CHANGES.rst') ) setup( name='zc.lockfile', version=version, author="Zope Foundation", author_email="zope-dev@zope.dev", description="Basic inter-process locks", long_description=long_description, license="ZPL-2.1", keywords="lock", url='https://github.com/zopefoundation/zc.lockfile', python_requires='>=3.9', install_requires='setuptools', extras_require=dict( test=[ 'zope.testing', ]), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Natural Language :: English', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development', ], include_package_data=True, zip_safe=False, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1776493 zc_lockfile-4.0/src/0000755000076600000240000000000015062732617014144 5ustar00m.howitzstaff././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1776943 zc_lockfile-4.0/src/zc/0000755000076600000240000000000015062732617014560 5ustar00m.howitzstaff././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1803663 zc_lockfile-4.0/src/zc/lockfile/0000755000076600000240000000000015062732617016350 5ustar00m.howitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/src/zc/lockfile/README.txt0000644000076600000240000000400015062732616020037 0ustar00m.howitzstaffLock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print("Can't lock file") Can't lock file .. We don't log failure to acquire. >>> for record in handler.records: # doctest: +ELLIPSIS ... print(record.levelname+' '+record.getMessage()) To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Hostname in lock file ===================== In a container environment (e.g. Docker), the PID is typically always identical even if multiple containers are running under the same operating system instance. Clearly, inspecting lock files doesn't then help much in debugging. To identify the container which created the lock file, we need information about the container in the lock file. Since Docker uses the container identifier or name as the hostname, this information can be stored in the lock file in addition to or instead of the PID. Use the ``content_template`` keyword argument to ``LockFile`` to specify a custom lock file content format: >>> lock = zc.lockfile.LockFile('lock', content_template='{pid};{hostname}') >>> lock.close() If you now inspected the lock file, you would see e.g.: $ cat lock 123;myhostname ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/src/zc/lockfile/__init__.py0000644000076600000240000000701315062732616020461 0ustar00m.howitzstaff############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import logging import os logger = logging.getLogger("zc.lockfile") class LockError(Exception): """Couldn't get a lock """ try: import fcntl except ModuleNotFoundError: # pragma: no cover try: import msvcrt except ModuleNotFoundError: # pragma: no cover def _lock_file(file): raise TypeError('No file-locking support on this platform') def _unlock_file(file): raise TypeError('No file-locking support on this platform') else: # Windows def _lock_file(file): # Lock just the first byte try: msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, 1) except OSError: raise LockError("Couldn't lock %r" % file.name) def _unlock_file(file): try: file.seek(0) msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1) except OSError: raise LockError("Couldn't unlock %r" % file.name) else: # Unix _flags = fcntl.LOCK_EX | fcntl.LOCK_NB def _lock_file(file): try: fcntl.flock(file.fileno(), _flags) except OSError: raise LockError("Couldn't lock %r" % file.name) def _unlock_file(file): fcntl.flock(file.fileno(), fcntl.LOCK_UN) class LazyHostName: """Avoid importing socket and calling gethostname() unnecessarily""" def __str__(self): import socket return socket.gethostname() class SimpleLockFile: _fp = None def __init__(self, path): self._path = path try: # Try to open for writing without truncation: fp = open(path, 'r+') except OSError: # If the file doesn't exist, we'll get an IO error, try a+ # Note that there may be a race here. Multiple processes # could fail on the r+ open and open the file a+, but only # one will get the the lock and write a pid. fp = open(path, 'a+') try: _lock_file(fp) self._fp = fp except BaseException: fp.close() raise # Lock acquired self._on_lock() fp.flush() def close(self): if self._fp is None: pass # pragma: no cover else: _unlock_file(self._fp) self._fp.close() self._fp = None def _on_lock(self): """ Allow subclasses to supply behavior to occur following lock acquisition. """ class LockFile(SimpleLockFile): def __init__(self, path, content_template='{pid}'): self._content_template = content_template super().__init__(path) def _on_lock(self): content = self._content_template.format( pid=os.getpid(), hostname=LazyHostName(), ) self._fp.write(" %s\n" % content) self._fp.truncate() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/src/zc/lockfile/tests.py0000644000076600000240000001246415062732616020072 0ustar00m.howitzstaff############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import os import tempfile import threading import time import unittest from unittest.mock import Mock from unittest.mock import patch from zope.testing import setupstack import zc.lockfile def inc(): while True: try: lock = zc.lockfile.LockFile('f.lock') except zc.lockfile.LockError: continue else: break f = open('f', 'r+b') v = int(f.readline().strip()) time.sleep(0.01) v += 1 f.seek(0) f.write(('%d\n' % v).encode('ASCII')) f.close() lock.close() def many_threads_read_and_write(): r""" >>> with open('f', 'w+b') as file: ... _ = file.write(b'0\n') >>> with open('f.lock', 'w+b') as file: ... _ = file.write(b'0\n') >>> n = 50 >>> threads = [threading.Thread(target=inc) for i in range(n)] >>> _ = [thread.start() for thread in threads] >>> _ = [thread.join() for thread in threads] >>> with open('f', 'rb') as file: ... saved = int(file.read().strip()) >>> saved == n True >>> os.remove('f') We should only have one pid in the lock file: >>> f = open('f.lock') >>> len(f.read().strip().split()) 1 >>> f.close() >>> os.remove('f.lock') """ def pid_in_lockfile(): r""" >>> import os, zc.lockfile >>> pid = os.getpid() >>> lock = zc.lockfile.LockFile("f.lock") >>> f = open("f.lock") >>> _ = f.seek(1) >>> f.read().strip() == str(pid) True >>> f.close() Make sure that locking twice does not overwrite the old pid: >>> lock = zc.lockfile.LockFile("f.lock") Traceback (most recent call last): ... zc.lockfile.LockError: Couldn't lock 'f.lock' >>> f = open("f.lock") >>> _ = f.seek(1) >>> f.read().strip() == str(pid) True >>> f.close() >>> lock.close() """ def hostname_in_lockfile(): r""" hostname is correctly written into the lock file when it's included in the lock file content template >>> import zc.lockfile >>> with patch('socket.gethostname', Mock(return_value='myhostname')): ... lock = zc.lockfile.LockFile( ... "f.lock", content_template='{hostname}') >>> f = open("f.lock") >>> _ = f.seek(1) >>> f.read().rstrip() 'myhostname' >>> f.close() Make sure that locking twice does not overwrite the old hostname: >>> lock = zc.lockfile.LockFile("f.lock", content_template='{hostname}') Traceback (most recent call last): ... zc.lockfile.LockError: Couldn't lock 'f.lock' >>> f = open("f.lock") >>> _ = f.seek(1) >>> f.read().rstrip() 'myhostname' >>> f.close() >>> lock.close() """ class LockFileLogEntryTestCase(unittest.TestCase): """Tests for logging in case of lock failure""" def setUp(self): self.here = os.getcwd() self.tmp = tempfile.mkdtemp(prefix='zc.lockfile-test-') os.chdir(self.tmp) def tearDown(self): os.chdir(self.here) setupstack.rmtree(self.tmp) def test_log_formatting(self): # PID and hostname are parsed and logged from lock file on failure with patch('os.getpid', Mock(return_value=123)): with patch('socket.gethostname', Mock(return_value='myhostname')): lock = zc.lockfile.LockFile( 'f.lock', content_template='{pid}/{hostname}') with open('f.lock') as f: self.assertEqual(' 123/myhostname\n', f.read()) lock.close() def test_unlock_and_lock_while_multiprocessing_process_running(self): import multiprocessing lock = zc.lockfile.LockFile('l') q = multiprocessing.Queue() p = multiprocessing.Process(target=q.get) p.daemon = True p.start() # release and re-acquire should work (obviously) lock.close() lock = zc.lockfile.LockFile('l') self.assertTrue(p.is_alive()) q.put(0) lock.close() p.join() def test_simple_lock(self): assert isinstance(zc.lockfile.SimpleLockFile, type) lock = zc.lockfile.SimpleLockFile('s') with self.assertRaises(zc.lockfile.LockError): zc.lockfile.SimpleLockFile('s') lock.close() zc.lockfile.SimpleLockFile('s').close() def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocFileSuite( 'README.txt', setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown)) suite.addTest(doctest.DocTestSuite( setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown)) # Add unittest test cases from this module suite.addTest(unittest.defaultTestLoader.loadTestsFromName(__name__)) return suite ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1758180751.1805785 zc_lockfile-4.0/src/zc.lockfile.egg-info/0000755000076600000240000000000015062732617020041 5ustar00m.howitzstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/PKG-INFO0000644000076600000240000001472715062732617021151 0ustar00m.howitzstaffMetadata-Version: 2.4 Name: zc.lockfile Version: 4.0 Summary: Basic inter-process locks Home-page: https://github.com/zopefoundation/zc.lockfile Author: Zope Foundation Author-email: zope-dev@zope.dev License: ZPL-2.1 Keywords: lock Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Requires-Python: >=3.9 License-File: LICENSE.txt Requires-Dist: setuptools Provides-Extra: test Requires-Dist: zope.testing; extra == "test" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: provides-extra Dynamic: requires-dist Dynamic: requires-python Dynamic: summary ************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print("Can't lock file") Can't lock file .. We don't log failure to acquire. >>> for record in handler.records: # doctest: +ELLIPSIS ... print(record.levelname+' '+record.getMessage()) To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Hostname in lock file ===================== In a container environment (e.g. Docker), the PID is typically always identical even if multiple containers are running under the same operating system instance. Clearly, inspecting lock files doesn't then help much in debugging. To identify the container which created the lock file, we need information about the container in the lock file. Since Docker uses the container identifier or name as the hostname, this information can be stored in the lock file in addition to or instead of the PID. Use the ``content_template`` keyword argument to ``LockFile`` to specify a custom lock file content format: >>> lock = zc.lockfile.LockFile('lock', content_template='{pid};{hostname}') >>> lock.close() If you now inspected the lock file, you would see e.g.: $ cat lock 123;myhostname Change History *************** 4.0 (2025-09-18) ================ - Replace ``pkg_resources`` namespace with PEP 420 native namespace. - Add support for Python 3.12, 3.13. - Drop support for Python 3.7, 3.8. 3.0.post1 (2023-02-28) ====================== - Add ``python_requires`` to ``setup.py`` to prevent installing on not supported old Python versions. 3.0 (2023-02-23) ================ - Add support for Python 3.9, 3.10, 3.11. - Drop support for Python 2.7, 3.5, 3.6. - Drop support for deprecated ``python setup.py test``. 2.0 (2019-08-08) ================ - Extracted new ``SimpleLockFile`` that removes implicit behavior writing to the lock file, and instead allows a subclass to define that behavior. (`#15 `_) - ``SimpleLockFile`` and thus ``LockFile`` are now new-style classes. Any clients relying on ``LockFile`` being an old-style class will need to be adapted. - Drop support for Python 3.4. - Add support for Python 3.8b3. 1.4 (2018-11-12) ================ - Claim support for Python 3.6 and 3.7. - Drop Python 2.6 and 3.3. 1.3.0 (2018-04-23) ================== - Stop logging failure to acquire locks. Clients can do that if they wish. - Claim support for Python 3.4 and 3.5. - Drop Python 3.2 support because pip no longer supports it. 1.2.1 (2016-06-19) ================== - Fixed: unlocking and locking didn't work when a multiprocessing process was running (and presumably other conditions). 1.2.0 (2016-06-09) ================== - Added the ability to include the hostname in the lock file content. - Code and ReST markup cosmetics. [alecghica] 1.1.0 (2013-02-12) ================== - Added Trove classifiers and made setup.py zest.releaser friendly. - Added Python 3.2, 3.3 and PyPy 1.9 support. - Removed Python 2.4 and Python 2.5 support. 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== - Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/SOURCES.txt0000644000076600000240000000070615062732617021730 0ustar00m.howitzstaff.pre-commit-config.yaml CHANGES.rst CONTRIBUTING.md COPYRIGHT.txt LICENSE.txt MANIFEST.in README.rst pyproject.toml setup.cfg setup.py tox.ini src/zc.lockfile.egg-info/PKG-INFO src/zc.lockfile.egg-info/SOURCES.txt src/zc.lockfile.egg-info/dependency_links.txt src/zc.lockfile.egg-info/not-zip-safe src/zc.lockfile.egg-info/requires.txt src/zc.lockfile.egg-info/top_level.txt src/zc/lockfile/README.txt src/zc/lockfile/__init__.py src/zc/lockfile/tests.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/dependency_links.txt0000644000076600000240000000000115062732617024107 0ustar00m.howitzstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/not-zip-safe0000644000076600000240000000000115062732617022267 0ustar00m.howitzstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/requires.txt0000644000076600000240000000004015062732617022433 0ustar00m.howitzstaffsetuptools [test] zope.testing ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180751.0 zc_lockfile-4.0/src/zc.lockfile.egg-info/top_level.txt0000644000076600000240000000000315062732617022564 0ustar00m.howitzstaffzc ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1758180750.0 zc_lockfile-4.0/tox.ini0000644000076600000240000000275515062732616014700 0ustar00m.howitzstaff# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [tox] minversion = 3.18 envlist = release-check lint py39 py310 py311 py312 py313 pypy3 coverage [testenv] usedevelop = true package = wheel wheel_build_env = .pkg deps = setuptools == 78.1.1 zope.testrunner commands = zope-testrunner --test-path=src {posargs:-vc} extras = test [testenv:setuptools-latest] basepython = python3 deps = git+https://github.com/pypa/setuptools.git\#egg=setuptools zope.testrunner [testenv:release-check] description = ensure that the distribution is ready to release basepython = python3 skip_install = true deps = setuptools == 78.1.1 wheel twine build check-manifest check-python-versions >= 0.20.0 wheel commands_pre = commands = check-manifest check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml python -m build --sdist --no-isolation twine check dist/* [testenv:lint] description = This env runs all linters configured in .pre-commit-config.yaml basepython = python3 skip_install = true deps = pre-commit commands_pre = commands = pre-commit run --all-files --show-diff-on-failure [testenv:coverage] basepython = python3 allowlist_externals = mkdir deps = coverage[toml] zope.testrunner commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage html coverage report