zc.lockfile-2.0/0000755000076500000240000000000013522734122013442 5ustar macstaff00000000000000zc.lockfile-2.0/.gitignore0000644000076500000240000000004113522734122015425 0ustar macstaff00000000000000*.egg-info/ *.pyc /.eggs/ /.tox/ zc.lockfile-2.0/.travis.yml0000644000076500000240000000032613522734122015554 0ustar macstaff00000000000000language: python dist: xenial python: - 2.7 - 3.5 - 3.6 - 3.7 - 3.8-dev - pypy - pypy3 install: - pip install . script: - python setup.py test -q notifications: email: false zc.lockfile-2.0/CHANGES.rst0000644000076500000240000000370213522734122015246 0ustar macstaff00000000000000Change History *************** 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 zc.lockfile-2.0/COPYRIGHT.txt0000644000076500000240000000004013522734122015545 0ustar macstaff00000000000000Zope Foundation and Contributorszc.lockfile-2.0/LICENSE.txt0000644000076500000240000000402613522734122015267 0ustar macstaff00000000000000Zope 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. zc.lockfile-2.0/MANIFEST.in0000644000076500000240000000020513522734122015175 0ustar macstaff00000000000000include *.rst include *.txt include tox.ini include bootstrap.py include buildout.cfg recursive-include src * global-exclude *.pyc zc.lockfile-2.0/PKG-INFO0000644000076500000240000001610613522734122014543 0ustar macstaff00000000000000Metadata-Version: 2.1 Name: zc.lockfile Version: 2.0 Summary: Basic inter-process locks Home-page: https://github.com/zopefoundation/zc.lockfile Author: Zope Foundation Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ************************* 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 *************** 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 Keywords: lock Platform: UNKNOWN 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 :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Provides-Extra: test zc.lockfile-2.0/README.rst0000644000076500000240000000110713522734122015130 0ustar macstaff00000000000000************************* 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:: zc.lockfile-2.0/bootstrap.py0000644000076500000240000001644213522734122016040 0ustar macstaff00000000000000############################################################################## # # Copyright (c) 2006 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. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser __version__ = '2015-07-01' # See zc.buildout's changelog if this version is up to date. tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("--version", action="store_true", default=False, help=("Return bootstrap.py version.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) parser.add_option("--allow-site-packages", action="store_true", default=False, help=("Let bootstrap.py use existing site packages")) parser.add_option("--buildout-version", help="Use a specific zc.buildout version") parser.add_option("--setuptools-version", help="Use a specific setuptools version") parser.add_option("--setuptools-to-dir", help=("Allow for re-use of existing directory of " "setuptools versions")) options, args = parser.parse_args() if options.version: print("bootstrap.py version %s" % __version__) sys.exit(0) ###################################################################### # load/install setuptools try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen ez = {} if os.path.exists('ez_setup.py'): exec(open('ez_setup.py').read(), ez) else: exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) if not options.allow_site_packages: # ez_setup imports site, which adds site packages # this will remove them from the path to ensure that incompatible versions # of setuptools are not in the path import site # inside a virtualenv, there is no 'getsitepackages'. # We can't remove these reliably if hasattr(site, 'getsitepackages'): for sitepackage_path in site.getsitepackages(): # Strip all site-packages directories from sys.path that # are not sys.prefix; this is because on Windows # sys.prefix is a site-package directory. if sitepackage_path != sys.prefix: sys.path[:] = [x for x in sys.path if sitepackage_path not in x] setup_args = dict(to_dir=tmpeggs, download_delay=0) if options.setuptools_version is not None: setup_args['version'] = options.setuptools_version if options.setuptools_to_dir is not None: setup_args['to_dir'] = options.setuptools_to_dir ez['use_setuptools'](**setup_args) import setuptools import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location # Fix sys.path here as easy_install.pth added before PYTHONPATH cmd = [sys.executable, '-c', 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) requirement = 'zc.buildout' version = options.buildout_version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): try: return not parsed_version.is_prerelease except AttributeError: # Older setuptools for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd) != 0: raise Exception( "Failed to execute command:\n%s" % repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zc.lockfile-2.0/buildout.cfg0000644000076500000240000000025413522734122015753 0ustar macstaff00000000000000[buildout] develop = . parts = py [test] recipe = zc.recipe.testrunner ==1.3.0 eggs = zc.lockfile [test] [py] recipe = zc.recipe.egg eggs = ${test:eggs} interpreter = py zc.lockfile-2.0/setup.cfg0000644000076500000240000000010313522734122015255 0ustar macstaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 zc.lockfile-2.0/setup.py0000644000076500000240000000544413522734122015163 0ustar macstaff00000000000000############################################################################## # # Copyright (c) Zope Corporation 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. # ############################################################################## version = '2.0' import os from setuptools import setup, find_packages import sys # Use unittest.mock in Python >= 3.3, or the mock package from PyPI on < 3.3 CONDITIONAL_TEST_REQUIREMENTS = ['mock'] if sys.version_info < (3, 3) else [] 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.org", description = "Basic inter-process locks", long_description=long_description, license = "ZPL 2.1", keywords = "lock", url='https://github.com/zopefoundation/zc.lockfile', packages = find_packages('src'), package_dir = {'': 'src'}, namespace_packages = ['zc'], install_requires = 'setuptools', extras_require=dict( test=CONDITIONAL_TEST_REQUIREMENTS + [ '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 :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development', ], test_suite="zc.lockfile.tests.test_suite", tests_require=CONDITIONAL_TEST_REQUIREMENTS + [ 'zope.testing'], include_package_data = True, zip_safe=False, ) zc.lockfile-2.0/src/0000755000076500000240000000000013522734122014231 5ustar macstaff00000000000000zc.lockfile-2.0/src/zc/0000755000076500000240000000000013522734122014645 5ustar macstaff00000000000000zc.lockfile-2.0/src/zc/__init__.py0000644000076500000240000000007013522734122016753 0ustar macstaff00000000000000__import__('pkg_resources').declare_namespace(__name__) zc.lockfile-2.0/src/zc/lockfile/0000755000076500000240000000000013522734122016435 5ustar macstaff00000000000000zc.lockfile-2.0/src/zc/lockfile/README.txt0000644000076500000240000000400013522734122020125 0ustar macstaff00000000000000Lock 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 zc.lockfile-2.0/src/zc/lockfile/__init__.py0000644000076500000240000000670113522734122020552 0ustar macstaff00000000000000############################################################################## # # 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 os import errno import logging logger = logging.getLogger("zc.lockfile") __metaclass__ = type class LockError(Exception): """Couldn't get a lock """ try: import fcntl except ImportError: try: import msvcrt except ImportError: 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 IOError: 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 IOError: 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 IOError: 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 IOError: # 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: fp.close() raise # Lock acquired self._on_lock() fp.flush() def close(self): if self._fp is not None: _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(LockFile, self).__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() zc.lockfile-2.0/src/zc/lockfile/tests.py0000644000076500000240000001326613522734122020161 0ustar macstaff00000000000000############################################################################## # # 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 os, re, sys, unittest, doctest import zc.lockfile, time, threading from zope.testing import renormalizing, setupstack import tempfile try: from unittest.mock import Mock, patch except ImportError: from mock import Mock, patch checker = renormalizing.RENormalizing([ # Python 3 adds module path to error class name. (re.compile("zc\.lockfile\.LockError:"), r"LockError:"), ]) def inc(): while 1: 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): ... 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): ... LockError: Couldn't lock 'f.lock' >>> f = open("f.lock") >>> _ = f.seek(1) >>> f.read().rstrip() 'myhostname' >>> f.close() >>> lock.close() """ class TestLogger(object): def __init__(self): self.log_entries = [] def exception(self, msg, *args): self.log_entries.append((msg,) + args) 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', checker=checker, setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown)) suite.addTest(doctest.DocTestSuite( setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown, checker=checker)) # Add unittest test cases from this module suite.addTest(unittest.defaultTestLoader.loadTestsFromName(__name__)) return suite zc.lockfile-2.0/src/zc.lockfile.egg-info/0000755000076500000240000000000013522734122020126 5ustar macstaff00000000000000zc.lockfile-2.0/src/zc.lockfile.egg-info/PKG-INFO0000644000076500000240000001610613522734122021227 0ustar macstaff00000000000000Metadata-Version: 2.1 Name: zc.lockfile Version: 2.0 Summary: Basic inter-process locks Home-page: https://github.com/zopefoundation/zc.lockfile Author: Zope Foundation Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ************************* 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 *************** 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 Keywords: lock Platform: UNKNOWN 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 :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Provides-Extra: test zc.lockfile-2.0/src/zc.lockfile.egg-info/SOURCES.txt0000644000076500000240000000100313522734122022004 0ustar macstaff00000000000000.gitignore .travis.yml CHANGES.rst COPYRIGHT.txt LICENSE.txt MANIFEST.in README.rst bootstrap.py buildout.cfg setup.cfg setup.py tox.ini src/zc/__init__.py 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/namespace_packages.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.pyzc.lockfile-2.0/src/zc.lockfile.egg-info/dependency_links.txt0000644000076500000240000000000113522734122024174 0ustar macstaff00000000000000 zc.lockfile-2.0/src/zc.lockfile.egg-info/namespace_packages.txt0000644000076500000240000000000313522734122024452 0ustar macstaff00000000000000zc zc.lockfile-2.0/src/zc.lockfile.egg-info/not-zip-safe0000644000076500000240000000000113522734122022354 0ustar macstaff00000000000000 zc.lockfile-2.0/src/zc.lockfile.egg-info/requires.txt0000644000076500000240000000004013522734122022520 0ustar macstaff00000000000000setuptools [test] zope.testing zc.lockfile-2.0/src/zc.lockfile.egg-info/top_level.txt0000644000076500000240000000000313522734122022651 0ustar macstaff00000000000000zc zc.lockfile-2.0/tox.ini0000644000076500000240000000113513522734122014755 0ustar macstaff00000000000000[tox] envlist = py27,py35,py36,py37,py38,pypy,pypy3 [testenv] commands = python setup.py test -q # without explicit deps, setup.py test will download a bunch of eggs into $PWD deps = zope.testing [testenv:coverage] basepython = python2.7 commands = # The installed version messes up nose's test discovery / coverage reporting # So, we uninstall that from the environment, and then install the editable # version, before running nosetests. pip uninstall -y zc.lockfile pip install -e . nosetests --with-xunit --with-xcoverage deps = nose coverage nosexcover