Farewell pylockfile
We need remove all of the files except the README as required by: https://docs.openstack.org/infra/manual/drivers.html#step-2-remove-project-content Depends-On:I4501bc69b6d6b60894008a796d2888ac52bec252 Change-Id: I2cfeaa7fabd16c43e1301560bb2b71c8b98d49e2
This commit is contained in:
parent
99870bfa02
commit
f3453b829a
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,11 +0,0 @@
|
|||||||
dist
|
|
||||||
.tox
|
|
||||||
*.pyc
|
|
||||||
*~
|
|
||||||
doc/build
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
*.egg
|
|
||||||
pylockfile.egg-info
|
|
||||||
cover
|
|
||||||
.coverage
|
|
@ -1,4 +0,0 @@
|
|||||||
[gerrit]
|
|
||||||
host=review.openstack.org
|
|
||||||
port=29418
|
|
||||||
project=openstack/pylockfile.git
|
|
6
ACKS
6
ACKS
@ -1,6 +0,0 @@
|
|||||||
Thanks to the following people for help with lockfile.
|
|
||||||
|
|
||||||
Scott Dial
|
|
||||||
Ben Finney
|
|
||||||
Frank Niessink
|
|
||||||
Konstantin Veretennicov
|
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
||||||
|
|
||||||
Copyright (c) 2007 Skip Montanaro.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
44
README.rst
44
README.rst
@ -1,41 +1,15 @@
|
|||||||
.. warning::
|
This project is no longer maintained.
|
||||||
|
|
||||||
**This package is deprecated**. It is highly preferred that instead of
|
The contents of this repository are still available in the Git
|
||||||
using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
|
source code management system. To see the contents of this
|
||||||
used instead.
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
The lockfile package exports a ``LockFile`` class which provides a simple API
|
For an alternative project, please see `fasteners`_ or `oslo.concurrency`
|
||||||
for locking files. Unlike the Windows ``msvcrt.locking`` function, the
|
|
||||||
``fcntl.lockf`` and ``fcntl.flock`` functions, and the deprecated ``posixfile
|
|
||||||
module``, the API is identical across both UNIX (including Linux and Mac) and
|
|
||||||
Windows platforms.
|
|
||||||
|
|
||||||
The lock mechanism relies on the atomic nature of the link (on UNIX) and
|
For any further questions, please email
|
||||||
``mkdir`` (on Windows) system calls. An implementation based on SQLite is also
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
provided, more as a demonstration of the possibilities it provides than as
|
Freenode.
|
||||||
production-quality code.
|
|
||||||
|
|
||||||
Install pylockfile with: ``pip install lockfile``.
|
|
||||||
|
|
||||||
* `Documentation <http://docs.openstack.org/developer/pylockfile>`_
|
|
||||||
* `Source <http://git.openstack.org/cgit/openstack/pylockfile>`_
|
|
||||||
* `Bugs <http://bugs.launchpad.net/pylockfile>`_
|
|
||||||
|
|
||||||
For any questions or comments or further help needed please email
|
|
||||||
`openstack-dev`_ and prefix your email subject with ``[oslo][pylockfile]`` (for
|
|
||||||
a faster response).
|
|
||||||
|
|
||||||
In version 0.9 the API changed in two significant ways:
|
|
||||||
|
|
||||||
* It changed from a module defining several classes to a package containing
|
|
||||||
several modules, each defining a single class.
|
|
||||||
|
|
||||||
* Where classes had been named ``SomethingFileLock`` before the last two words
|
|
||||||
have been reversed, so that class is now ``SomethingLockFile``.
|
|
||||||
|
|
||||||
The previous module-level definitions of ``LinkFileLock``, ``MkdirFileLock``
|
|
||||||
and ``SQLiteFileLock`` will be retained until the 1.0 release.
|
|
||||||
|
|
||||||
.. _fasteners: https://pypi.python.org/pypi/fasteners
|
.. _fasteners: https://pypi.python.org/pypi/fasteners
|
||||||
.. _oslo.concurrency: http://docs.openstack.org/developer/oslo.concurrency/
|
.. _oslo.concurrency: http://docs.openstack.org/developer/oslo.concurrency/
|
||||||
.. _openstack-dev: http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
Version 0.9.1
|
|
||||||
=============
|
|
||||||
|
|
||||||
* This release moves the source location to Google Code.
|
|
||||||
|
|
||||||
* Threaded support is currently broken. (It might not actually be broken.
|
|
||||||
It might just be the tests which are broken.)
|
|
||||||
|
|
||||||
Version 0.9
|
|
||||||
===========
|
|
||||||
|
|
||||||
* The lockfile module was reorganized into a package.
|
|
||||||
|
|
||||||
* The names of the three main classes have changed as follows:
|
|
||||||
|
|
||||||
LinkFileLock -> LinkLockFile
|
|
||||||
MkdirFileLock -> MkdirLockFile
|
|
||||||
SQLiteFileLock -> SQLiteLockFile
|
|
||||||
|
|
||||||
* A PIDLockFile class was added.
|
|
||||||
|
|
||||||
Version 0.3
|
|
||||||
===========
|
|
||||||
|
|
||||||
* Fix 2.4.diff file error.
|
|
||||||
|
|
||||||
* More documentation updates.
|
|
||||||
|
|
||||||
Version 0.2
|
|
||||||
===========
|
|
||||||
|
|
||||||
* Added 2.4.diff file to patch lockfile to work with Python 2.4 (removes use
|
|
||||||
of with statement).
|
|
||||||
|
|
||||||
* Renamed _FileLock base class to LockBase to expose it (and its docstrings)
|
|
||||||
to pydoc.
|
|
||||||
|
|
||||||
* Got rid of time.sleep() calls in tests (thanks to Konstantin
|
|
||||||
Veretennicov).
|
|
||||||
|
|
||||||
* Use thread.get_ident() as the thread discriminator.
|
|
||||||
|
|
||||||
* Updated documentation a bit.
|
|
||||||
|
|
||||||
* Added RELEASE-NOTES.
|
|
||||||
|
|
||||||
Version 0.1
|
|
||||||
===========
|
|
||||||
|
|
||||||
* First release - All basic functionality there.
|
|
@ -1,73 +0,0 @@
|
|||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " pickle to make pickle files (usable by e.g. sphinx-web)"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " changes to make an overview over all changed/added/deprecated items"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-rm -rf .build/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
mkdir -p .build/html .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in .build/html."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
mkdir -p .build/pickle .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files or run"
|
|
||||||
@echo " sphinx-web .build/pickle"
|
|
||||||
@echo "to start the sphinx-web server."
|
|
||||||
|
|
||||||
web: pickle
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
mkdir -p .build/htmlhelp .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in .build/htmlhelp."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
mkdir -p .build/latex .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in .build/latex."
|
|
||||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
|
||||||
"run these through (pdf)latex."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
mkdir -p .build/changes .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in .build/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
mkdir -p .build/linkcheck .build/doctrees
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in .build/linkcheck/output.txt."
|
|
||||||
|
|
||||||
html.zip: html
|
|
||||||
(cd .build/html ; zip -r ../../$@ *)
|
|
@ -1,179 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# lockfile documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Sat Sep 13 17:54:17 2008.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its containing dir.
|
|
||||||
#
|
|
||||||
# The contents of this file are pickled, so don't put values in the namespace
|
|
||||||
# that aren't pickleable (module imports are okay, they're removed automatically).
|
|
||||||
#
|
|
||||||
# All configuration values have a default value; values that are commented out
|
|
||||||
# serve to show the default value.
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
|
|
||||||
# If your extensions are in another directory, add it here. If the directory
|
|
||||||
# is relative to the documentation root, use os.path.abspath to make it
|
|
||||||
# absolute, like shown here.
|
|
||||||
#sys.path.append(os.path.abspath('some/directory'))
|
|
||||||
|
|
||||||
# General configuration
|
|
||||||
# ---------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = []
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['.templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General substitutions.
|
|
||||||
project = 'lockfile'
|
|
||||||
copyright = '2008, Skip Montanaro'
|
|
||||||
|
|
||||||
# The default replacements for |version| and |release|, also used in various
|
|
||||||
# other places throughout the built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '0.3'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = '0.3'
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of documents that shouldn't be included in the build.
|
|
||||||
#unused_docs = []
|
|
||||||
|
|
||||||
# List of directories, relative to source directories, that shouldn't be searched
|
|
||||||
# for source files.
|
|
||||||
#exclude_dirs = []
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
|
|
||||||
# Options for HTML output
|
|
||||||
# -----------------------
|
|
||||||
|
|
||||||
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
|
||||||
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
|
||||||
# given in html_static_path.
|
|
||||||
html_style = 'default.css'
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
#html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to place at the top of
|
|
||||||
# the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
#html_static_path = ['.static']
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_use_modindex = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
|
||||||
#html_copy_source = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = ''
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'lockfiledoc'
|
|
||||||
|
|
||||||
|
|
||||||
# Options for LaTeX output
|
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
|
||||||
#latex_paper_size = 'letter'
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#latex_font_size = '10pt'
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, document class [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('lockfile', 'lockfile.tex', 'lockfile Documentation',
|
|
||||||
'Skip Montanaro', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#latex_preamble = ''
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_use_modindex = True
|
|
@ -1,286 +0,0 @@
|
|||||||
|
|
||||||
:mod:`lockfile` --- Platform-independent file locking
|
|
||||||
=====================================================
|
|
||||||
|
|
||||||
.. module:: lockfile
|
|
||||||
:synopsis: Platform-independent file locking
|
|
||||||
.. moduleauthor:: Skip Montanaro <skip@pobox.com>
|
|
||||||
.. sectionauthor:: Skip Montanaro <skip@pobox.com>
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This package is **deprecated**. It is highly preferred that instead of
|
|
||||||
using this code base (where appropriate) that instead `fasteners`_ is
|
|
||||||
used instead. For any questions or comments or further help needed
|
|
||||||
please email `openstack-dev`_ and prefix your email subject
|
|
||||||
with ``[oslo][pylockfile]`` (for a faster response).
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This package is pre-release software. Between versions 0.8 and 0.9 it
|
|
||||||
was changed from a module to a package. It is quite possible that the
|
|
||||||
API and implementation will change again in important ways as people test
|
|
||||||
it and provide feedback and bug fixes. In particular, if the mkdir-based
|
|
||||||
locking scheme is sufficient for both Windows and Unix platforms, the
|
|
||||||
link-based scheme may be deleted so that only a single locking scheme is
|
|
||||||
used, providing cross-platform lockfile cooperation.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The implementation uses the `with` statement, both in the tests and in the
|
|
||||||
main code, so will only work out-of-the-box with Python 2.5 or later.
|
|
||||||
However, the use of the `with` statement is minimal, so if you apply the
|
|
||||||
patch in the included 2.4.diff file you can use it with Python 2.4. It's
|
|
||||||
possible that it will work in Python 2.3 with that patch applied as well,
|
|
||||||
though the doctest code relies on APIs new in 2.4, so will have to be
|
|
||||||
rewritten somewhat to allow testing on 2.3. As they say, patches welcome.
|
|
||||||
``;-)``
|
|
||||||
|
|
||||||
The :mod:`lockfile` package exports a :class:`LockFile` class which provides
|
|
||||||
a simple API for locking files. Unlike the Windows :func:`msvcrt.locking`
|
|
||||||
function, the Unix :func:`fcntl.flock`, :func:`fcntl.lockf` and the
|
|
||||||
deprecated :mod:`posixfile` module, the API is identical across both Unix
|
|
||||||
(including Linux and Mac) and Windows platforms. The lock mechanism relies
|
|
||||||
on the atomic nature of the :func:`link` (on Unix) and :func:`mkdir` (On
|
|
||||||
Windows) system calls. It also contains several lock-method-specific
|
|
||||||
modules: :mod:`lockfile.linklockfile`, :mod:`lockfile.mkdirlockfile`, and
|
|
||||||
:mod:`lockfile.sqlitelockfile`, each one exporting a single class. For
|
|
||||||
backwards compatibility with versions before 0.9 the :class:`LinkFileLock`,
|
|
||||||
:class:`MkdirFileLock` and :class:`SQLiteFileLock` objects are exposed as
|
|
||||||
attributes of the top-level lockfile package, though this use was deprecated
|
|
||||||
starting with version 0.9 and will be removed in version 1.0.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The current implementation uses :func:`os.link` on Unix, but since that
|
|
||||||
function is unavailable on Windows it uses :func:`os.mkdir` there. At
|
|
||||||
this point it's not clear that using the :func:`os.mkdir` method would be
|
|
||||||
insufficient on Unix systems. If it proves to be adequate on Unix then
|
|
||||||
the implementation could be simplified and truly cross-platform locking
|
|
||||||
would be possible.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The current implementation doesn't provide for shared vs. exclusive
|
|
||||||
locks. It should be possible for multiple reader processes to hold the
|
|
||||||
lock at the same time.
|
|
||||||
|
|
||||||
The module defines the following exceptions:
|
|
||||||
|
|
||||||
.. exception:: Error
|
|
||||||
|
|
||||||
This is the base class for all exceptions raised by the :class:`LockFile`
|
|
||||||
class.
|
|
||||||
|
|
||||||
.. exception:: LockError
|
|
||||||
|
|
||||||
This is the base class for all exceptions raised when attempting to lock
|
|
||||||
a file.
|
|
||||||
|
|
||||||
.. exception:: UnlockError
|
|
||||||
|
|
||||||
This is the base class for all exceptions raised when attempting to
|
|
||||||
unlock a file.
|
|
||||||
|
|
||||||
.. exception:: LockTimeout
|
|
||||||
|
|
||||||
This exception is raised if the :func:`LockFile.acquire` method is
|
|
||||||
called with a timeout which expires before an existing lock is released.
|
|
||||||
|
|
||||||
.. exception:: AlreadyLocked
|
|
||||||
|
|
||||||
This exception is raised if the :func:`LockFile.acquire` detects a
|
|
||||||
file is already locked when in non-blocking mode.
|
|
||||||
|
|
||||||
.. exception:: LockFailed
|
|
||||||
|
|
||||||
This exception is raised if the :func:`LockFile.acquire` detects some
|
|
||||||
other condition (such as a non-writable directory) which prevents it from
|
|
||||||
creating its lock file.
|
|
||||||
|
|
||||||
.. exception:: NotLocked
|
|
||||||
|
|
||||||
This exception is raised if the file is not locked when
|
|
||||||
:func:`LockFile.release` is called.
|
|
||||||
|
|
||||||
.. exception:: NotMyLock
|
|
||||||
|
|
||||||
This exception is raised if the file is locked by another thread or
|
|
||||||
process when :func:`LockFile.release` is called.
|
|
||||||
|
|
||||||
The following classes are provided:
|
|
||||||
|
|
||||||
.. class:: linklockfile.LinkLockFile(path, threaded=True)
|
|
||||||
|
|
||||||
This class uses the :func:`link(2)` system call as the basic lock
|
|
||||||
mechanism. *path* is an object in the file system to be locked. It need
|
|
||||||
not exist, but its directory must exist and be writable at the time the
|
|
||||||
:func:`acquire` and :func:`release` methods are called. *threaded* is
|
|
||||||
optional, but when set to :const:`True` locks will be distinguished
|
|
||||||
between threads in the same process.
|
|
||||||
|
|
||||||
.. class:: symlinklockfile.SymlinkLockFile(path, threaded=True)
|
|
||||||
|
|
||||||
This class uses the :func:`symlink(2)` system call as the basic lock
|
|
||||||
mechanism. The parameters have the same meaning and constraints as for
|
|
||||||
the :class:`LinkLockFile` class.
|
|
||||||
|
|
||||||
.. class:: mkdirlockfile.MkdirLockFile(path, threaded=True)
|
|
||||||
|
|
||||||
This class uses the :func:`mkdir(2)` system call as the basic lock
|
|
||||||
mechanism. The parameters have the same meaning and constraints as for
|
|
||||||
the :class:`LinkLockFile` class.
|
|
||||||
|
|
||||||
.. class:: sqlitelockfile.SQLiteLockFile(path, threaded=True)
|
|
||||||
|
|
||||||
This class uses the :mod:`sqlite3` module to implement the lock
|
|
||||||
mechanism. The parameters have the same meaning as for the
|
|
||||||
:class:`LinkLockFile` class.
|
|
||||||
|
|
||||||
.. class:: LockBase(path, threaded=True)
|
|
||||||
|
|
||||||
This is the base class for all concrete implementations and is available
|
|
||||||
at the lockfile package level so programmers can implement other locking
|
|
||||||
schemes.
|
|
||||||
|
|
||||||
.. function:: locked(path, timeout=None)
|
|
||||||
|
|
||||||
This function provides a decorator which insures the decorated function
|
|
||||||
is always called with the lock held.
|
|
||||||
|
|
||||||
By default, the :const:`LockFile` object refers to the
|
|
||||||
:class:`mkdirlockfile.MkdirLockFile` class on Windows. On all other
|
|
||||||
platforms it refers to the :class:`linklockfile.LinkLockFile` class.
|
|
||||||
|
|
||||||
When locking a file the :class:`linklockfile.LinkLockFile` class creates a
|
|
||||||
uniquely named hard link to an empty lock file. That hard link contains the
|
|
||||||
hostname, process id, and if locks between threads are distinguished, the
|
|
||||||
thread identifier. For example, if you want to lock access to a file named
|
|
||||||
"README", the lock file is named "README.lock". With per-thread locks
|
|
||||||
enabled the hard link is named HOSTNAME-THREADID-PID. With only per-process
|
|
||||||
locks enabled the hard link is named HOSTNAME--PID.
|
|
||||||
|
|
||||||
When using the :class:`mkdirlockfile.MkdirLockFile` class the lock file is a
|
|
||||||
directory. Referring to the example above, README.lock will be a directory
|
|
||||||
and HOSTNAME-THREADID-PID will be an empty file within that directory.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
Module :mod:`msvcrt`
|
|
||||||
Provides the :func:`locking` function, the standard Windows way of
|
|
||||||
locking (parts of) a file.
|
|
||||||
|
|
||||||
Module :mod:`posixfile`
|
|
||||||
The deprecated (since Python 1.5) way of locking files on Posix systems.
|
|
||||||
|
|
||||||
Module :mod:`fcntl`
|
|
||||||
Provides the current best way to lock files on Unix systems
|
|
||||||
(:func:`lockf` and :func:`flock`).
|
|
||||||
|
|
||||||
LockFile Objects
|
|
||||||
----------------
|
|
||||||
|
|
||||||
:class:`LockFile` objects support the `context manager` protocol used by the
|
|
||||||
statement:`with` statement. The timeout option is not supported when used in
|
|
||||||
this fashion. While support for timeouts could be implemented, there is no
|
|
||||||
support for handling the eventual :exc:`Timeout` exceptions raised by the
|
|
||||||
:func:`__enter__` method, so you would have to protect the `with` statement with
|
|
||||||
a `try` statement. The resulting construct would not be any simpler than just
|
|
||||||
using a `try` statement in the first place.
|
|
||||||
|
|
||||||
:class:`LockFile` has the following user-visible methods:
|
|
||||||
|
|
||||||
.. method:: LockFile.acquire(timeout=None)
|
|
||||||
|
|
||||||
Lock the file associated with the :class:`LockFile` object. If the
|
|
||||||
*timeout* is omitted or :const:`None` the caller will block until the
|
|
||||||
file is unlocked by the object currently holding the lock. If the
|
|
||||||
*timeout* is zero or a negative number the :exc:`AlreadyLocked` exception
|
|
||||||
will be raised if the file is currently locked by another process or
|
|
||||||
thread. If the *timeout* is positive, the caller will block for that
|
|
||||||
many seconds waiting for the lock to be released. If the lock is not
|
|
||||||
released within that period the :exc:`LockTimeout` exception will be
|
|
||||||
raised.
|
|
||||||
|
|
||||||
.. method:: LockFile.release()
|
|
||||||
|
|
||||||
Unlock the file associated with the :class:`LockFile` object. If the
|
|
||||||
file is not currently locked, the :exc:`NotLocked` exception is raised.
|
|
||||||
If the file is locked by another thread or process the :exc:`NotMyLock`
|
|
||||||
exception is raised.
|
|
||||||
|
|
||||||
.. method:: is_locked()
|
|
||||||
|
|
||||||
Return the status of the lock on the current file. If any process or
|
|
||||||
thread (including the current one) is locking the file, :const:`True` is
|
|
||||||
returned, otherwise :const:`False` is returned.
|
|
||||||
|
|
||||||
.. method:: break_lock()
|
|
||||||
|
|
||||||
If the file is currently locked, break it.
|
|
||||||
|
|
||||||
.. method:: i_am_locking()
|
|
||||||
|
|
||||||
Returns true if the caller holds the lock.
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
This example is the "hello world" for the :mod:`lockfile` package::
|
|
||||||
|
|
||||||
from lockfile import LockFile
|
|
||||||
lock = LockFile("/some/file/or/other")
|
|
||||||
with lock:
|
|
||||||
print lock.path, 'is locked.'
|
|
||||||
|
|
||||||
To use this with Python 2.4, you can execute::
|
|
||||||
|
|
||||||
from lockfile import LockFile
|
|
||||||
lock = LockFile("/some/file/or/other")
|
|
||||||
lock.acquire()
|
|
||||||
print lock.path, 'is locked.'
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
If you don't want to wait forever, you might try::
|
|
||||||
|
|
||||||
from lockfile import LockFile
|
|
||||||
lock = LockFile("/some/file/or/other")
|
|
||||||
while not lock.i_am_locking():
|
|
||||||
try:
|
|
||||||
lock.acquire(timeout=60) # wait up to 60 seconds
|
|
||||||
except LockTimeout:
|
|
||||||
lock.break_lock()
|
|
||||||
lock.acquire()
|
|
||||||
print "I locked", lock.path
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
You can also insure that a lock is always held when appropriately decorated
|
|
||||||
functions are called::
|
|
||||||
|
|
||||||
from lockfile import locked
|
|
||||||
@locked("/tmp/mylock")
|
|
||||||
def func(a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
Other Libraries
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The idea of implementing advisory locking with a standard API is not new
|
|
||||||
with :mod:`lockfile`. There are a number of other libraries available:
|
|
||||||
|
|
||||||
* locknix - http://pypi.python.org/pypi/locknix - Unix only
|
|
||||||
* mx.MiscLockFile - from Marc André Lemburg, part of the mx.Base
|
|
||||||
distribution - cross-platform.
|
|
||||||
* Twisted - http://twistedmatrix.com/trac/browser/trunk/twisted/python/lockfile.py
|
|
||||||
* zc.lockfile - http://pypi.python.org/pypi/zc.lockfile
|
|
||||||
|
|
||||||
|
|
||||||
Contacting the Author
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
If you encounter any problems with ``lockfile``, would like help or want to
|
|
||||||
submit a patch, check http://launchpad.net/pylockfile
|
|
||||||
|
|
||||||
|
|
||||||
.. _fasteners: http://fasteners.readthedocs.org/
|
|
||||||
.. _openstack-dev: mailto:openstack-dev@lists.openstack.org
|
|
@ -1,347 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
lockfile.py - Platform-independent advisory file locks.
|
|
||||||
|
|
||||||
Requires Python 2.5 unless you apply 2.4.diff
|
|
||||||
Locking is done on a per-thread basis instead of a per-process basis.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> try:
|
|
||||||
... lock.acquire()
|
|
||||||
... except AlreadyLocked:
|
|
||||||
... print 'somefile', 'is locked already.'
|
|
||||||
... except LockFailed:
|
|
||||||
... print 'somefile', 'can\\'t be locked.'
|
|
||||||
... else:
|
|
||||||
... print 'got lock'
|
|
||||||
got lock
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
True
|
|
||||||
>>> lock.release()
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
>>> with lock:
|
|
||||||
... print lock.is_locked()
|
|
||||||
True
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> # It is okay to lock twice from the same thread...
|
|
||||||
>>> with lock:
|
|
||||||
... lock.acquire()
|
|
||||||
...
|
|
||||||
>>> # Though no counter is kept, so you can't unlock multiple times...
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
|
|
||||||
Exceptions:
|
|
||||||
|
|
||||||
Error - base class for other exceptions
|
|
||||||
LockError - base class for all locking exceptions
|
|
||||||
AlreadyLocked - Another thread or process already holds the lock
|
|
||||||
LockFailed - Lock failed for some other reason
|
|
||||||
UnlockError - base class for all unlocking exceptions
|
|
||||||
AlreadyUnlocked - File was not locked.
|
|
||||||
NotMyLock - File was locked but not by the current thread/process
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
# Work with PEP8 and non-PEP8 versions of threading module.
|
|
||||||
if not hasattr(threading, "current_thread"):
|
|
||||||
threading.current_thread = threading.currentThread
|
|
||||||
if not hasattr(threading.Thread, "get_name"):
|
|
||||||
threading.Thread.get_name = threading.Thread.getName
|
|
||||||
|
|
||||||
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
|
|
||||||
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
|
|
||||||
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
|
|
||||||
'LockBase', 'locked']
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
"""
|
|
||||||
Base class for other exceptions.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise Error
|
|
||||||
... except Exception:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockError(Error):
|
|
||||||
"""
|
|
||||||
Base class for error arising from attempts to acquire the lock.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockError
|
|
||||||
... except Error:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockTimeout(LockError):
|
|
||||||
"""Raised when lock creation fails within a user-defined period of time.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockTimeout
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AlreadyLocked(LockError):
|
|
||||||
"""Some other thread/process is locking the file.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise AlreadyLocked
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockFailed(LockError):
|
|
||||||
"""Lock file creation failed for some other reason.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockFailed
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnlockError(Error):
|
|
||||||
"""
|
|
||||||
Base class for errors arising from attempts to release the lock.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise UnlockError
|
|
||||||
... except Error:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotLocked(UnlockError):
|
|
||||||
"""Raised when an attempt is made to unlock an unlocked file.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise NotLocked
|
|
||||||
... except UnlockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotMyLock(UnlockError):
|
|
||||||
"""Raised when an attempt is made to unlock a file someone else locked.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise NotMyLock
|
|
||||||
... except UnlockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class _SharedBase(object):
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
"""
|
|
||||||
Acquire the lock.
|
|
||||||
|
|
||||||
* If timeout is omitted (or None), wait forever trying to lock the
|
|
||||||
file.
|
|
||||||
|
|
||||||
* If timeout > 0, try to acquire the lock for that many seconds. If
|
|
||||||
the lock period expires and the file is still locked, raise
|
|
||||||
LockTimeout.
|
|
||||||
|
|
||||||
* If timeout <= 0, raise AlreadyLocked immediately if the file is
|
|
||||||
already locked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
"""
|
|
||||||
Release the lock.
|
|
||||||
|
|
||||||
If the file is not locked, raise NotLocked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""
|
|
||||||
Context manager support.
|
|
||||||
"""
|
|
||||||
self.acquire()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *_exc):
|
|
||||||
"""
|
|
||||||
Context manager support.
|
|
||||||
"""
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %r>" % (self.__class__.__name__, self.path)
|
|
||||||
|
|
||||||
|
|
||||||
class LockBase(_SharedBase):
|
|
||||||
"""Base class for platform-specific lock classes."""
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = LockBase('somefile')
|
|
||||||
>>> lock = LockBase('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
super(LockBase, self).__init__(path)
|
|
||||||
self.lock_file = os.path.abspath(path) + ".lock"
|
|
||||||
self.hostname = socket.gethostname()
|
|
||||||
self.pid = os.getpid()
|
|
||||||
if threaded:
|
|
||||||
t = threading.current_thread()
|
|
||||||
# Thread objects in Python 2.4 and earlier do not have ident
|
|
||||||
# attrs. Worm around that.
|
|
||||||
ident = getattr(t, "ident", hash(t))
|
|
||||||
self.tname = "-%x" % (ident & 0xffffffff)
|
|
||||||
else:
|
|
||||||
self.tname = ""
|
|
||||||
dirname = os.path.dirname(self.lock_file)
|
|
||||||
|
|
||||||
# unique name is mostly about the current process, but must
|
|
||||||
# also contain the path -- otherwise, two adjacent locked
|
|
||||||
# files conflict (one file gets locked, creating lock-file and
|
|
||||||
# unique file, the other one gets locked, creating lock-file
|
|
||||||
# and overwriting the already existing lock-file, then one
|
|
||||||
# gets unlocked, deleting both lock-file and unique file,
|
|
||||||
# finally the last lock errors out upon releasing.
|
|
||||||
self.unique_name = os.path.join(dirname,
|
|
||||||
"%s%s.%s%s" % (self.hostname,
|
|
||||||
self.tname,
|
|
||||||
self.pid,
|
|
||||||
hash(self.path)))
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
"""
|
|
||||||
Tell whether or not the file is locked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
"""
|
|
||||||
Return True if this object is locking the file.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
"""
|
|
||||||
Remove a lock. Useful if a locking thread failed to unlock.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
|
|
||||||
self.path)
|
|
||||||
|
|
||||||
|
|
||||||
def _fl_helper(cls, mod, *args, **kwds):
|
|
||||||
warnings.warn("Import from %s module instead of lockfile package" % mod,
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
# This is a bit funky, but it's only for awhile. The way the unit tests
|
|
||||||
# are constructed this function winds up as an unbound method, so it
|
|
||||||
# actually takes three args, not two. We want to toss out self.
|
|
||||||
if not isinstance(args[0], str):
|
|
||||||
# We are testing, avoid the first arg
|
|
||||||
args = args[1:]
|
|
||||||
if len(args) == 1 and not kwds:
|
|
||||||
kwds["threaded"] = True
|
|
||||||
return cls(*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def LinkFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import LinkLockFile from the
|
|
||||||
lockfile.linklockfile module.
|
|
||||||
"""
|
|
||||||
from . import linklockfile
|
|
||||||
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def MkdirFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import MkdirLockFile from the
|
|
||||||
lockfile.mkdirlockfile module.
|
|
||||||
"""
|
|
||||||
from . import mkdirlockfile
|
|
||||||
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def SQLiteFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import SQLiteLockFile from the
|
|
||||||
lockfile.mkdirlockfile module.
|
|
||||||
"""
|
|
||||||
from . import sqlitelockfile
|
|
||||||
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def locked(path, timeout=None):
|
|
||||||
"""Decorator which enables locks for decorated function.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: path for lockfile.
|
|
||||||
- timeout (optional): Timeout for acquiring lock.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
@locked('/var/run/myname', timeout=0)
|
|
||||||
def myname(...):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
def decor(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
lock = FileLock(path, timeout=timeout)
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
return wrapper
|
|
||||||
return decor
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(os, "link"):
|
|
||||||
from . import linklockfile as _llf
|
|
||||||
LockFile = _llf.LinkLockFile
|
|
||||||
else:
|
|
||||||
from . import mkdirlockfile as _mlf
|
|
||||||
LockFile = _mlf.MkdirLockFile
|
|
||||||
|
|
||||||
FileLock = LockFile
|
|
@ -1,73 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class LinkLockFile(LockBase):
|
|
||||||
"""Lock access to a file using atomic property of link(2).
|
|
||||||
|
|
||||||
>>> lock = LinkLockFile('somefile')
|
|
||||||
>>> lock = LinkLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
try:
|
|
||||||
open(self.unique_name, "wb").close()
|
|
||||||
except IOError:
|
|
||||||
raise LockFailed("failed to create %s" % self.unique_name)
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Try and create a hard link to it.
|
|
||||||
try:
|
|
||||||
os.link(self.unique_name, self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
# Link creation failed. Maybe we've double-locked?
|
|
||||||
nlinks = os.stat(self.unique_name).st_nlink
|
|
||||||
if nlinks == 2:
|
|
||||||
# The original link plus the one I created == 2. We're
|
|
||||||
# good to go.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Otherwise the lock creation failed.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
|
||||||
else:
|
|
||||||
# Link creation succeeded. We're good to go.
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not os.path.exists(self.unique_name):
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
os.unlink(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.exists(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (self.is_locked() and
|
|
||||||
os.path.exists(self.unique_name) and
|
|
||||||
os.stat(self.unique_name).st_nlink == 2)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.exists(self.lock_file):
|
|
||||||
os.unlink(self.lock_file)
|
|
@ -1,84 +0,0 @@
|
|||||||
from __future__ import absolute_import, division
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import errno
|
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class MkdirLockFile(LockBase):
|
|
||||||
"""Lock file by creating a directory."""
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = MkdirLockFile('somefile')
|
|
||||||
>>> lock = MkdirLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
# Lock file itself is a directory. Place the unique file name into
|
|
||||||
# it.
|
|
||||||
self.unique_name = os.path.join(self.lock_file,
|
|
||||||
"%s.%s%s" % (self.hostname,
|
|
||||||
self.tname,
|
|
||||||
self.pid))
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
wait = 0.1
|
|
||||||
else:
|
|
||||||
wait = max(0, timeout / 10)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
os.mkdir(self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
err = sys.exc_info()[1]
|
|
||||||
if err.errno == errno.EEXIST:
|
|
||||||
# Already locked.
|
|
||||||
if os.path.exists(self.unique_name):
|
|
||||||
# Already locked by me.
|
|
||||||
return
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
# Someone else has the lock.
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(wait)
|
|
||||||
else:
|
|
||||||
# Couldn't create the lock for some other reason
|
|
||||||
raise LockFailed("failed to create %s" % self.lock_file)
|
|
||||||
else:
|
|
||||||
open(self.unique_name, "wb").close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not os.path.exists(self.unique_name):
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
os.rmdir(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.exists(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (self.is_locked() and
|
|
||||||
os.path.exists(self.unique_name))
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.exists(self.lock_file):
|
|
||||||
for name in os.listdir(self.lock_file):
|
|
||||||
os.unlink(os.path.join(self.lock_file, name))
|
|
||||||
os.rmdir(self.lock_file)
|
|
@ -1,190 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# pidlockfile.py
|
|
||||||
#
|
|
||||||
# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
|
|
||||||
#
|
|
||||||
# This is free software: you may copy, modify, and/or distribute this work
|
|
||||||
# under the terms of the Python Software Foundation License, version 2 or
|
|
||||||
# later as published by the Python Software Foundation.
|
|
||||||
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
|
|
||||||
|
|
||||||
""" Lockfile behaviour implemented via Unix PID files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
|
|
||||||
LockTimeout)
|
|
||||||
|
|
||||||
|
|
||||||
class PIDLockFile(LockBase):
|
|
||||||
""" Lockfile implemented as a Unix PID file.
|
|
||||||
|
|
||||||
The lock file is a normal file named by the attribute `path`.
|
|
||||||
A lock's PID file contains a single line of text, containing
|
|
||||||
the process ID (PID) of the process that acquired the lock.
|
|
||||||
|
|
||||||
>>> lock = PIDLockFile('somefile')
|
|
||||||
>>> lock = PIDLockFile('somefile')
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=False, timeout=None):
|
|
||||||
# pid lockfiles don't support threaded operation, so always force
|
|
||||||
# False as the threaded arg.
|
|
||||||
LockBase.__init__(self, path, False, timeout)
|
|
||||||
self.unique_name = self.path
|
|
||||||
|
|
||||||
def read_pid(self):
|
|
||||||
""" Get the PID from the lock file.
|
|
||||||
"""
|
|
||||||
return read_pid_from_pidfile(self.path)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
""" Test if the lock is currently held.
|
|
||||||
|
|
||||||
The lock is held if the PID file for this lock exists.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return os.path.exists(self.path)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
""" Test if the lock is held by the current process.
|
|
||||||
|
|
||||||
Returns ``True`` if the current process ID matches the
|
|
||||||
number stored in the PID file.
|
|
||||||
"""
|
|
||||||
return self.is_locked() and os.getpid() == self.read_pid()
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
""" Acquire the lock.
|
|
||||||
|
|
||||||
Creates the PID file for this lock, or raises an error if
|
|
||||||
the lock could not be acquired.
|
|
||||||
"""
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
write_pid_to_pidfile(self.path)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.errno == errno.EEXIST:
|
|
||||||
# The lock creation failed. Maybe sleep a bit.
|
|
||||||
if time.time() > end_time:
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
|
||||||
else:
|
|
||||||
raise LockFailed("failed to create %s" % self.path)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
""" Release the lock.
|
|
||||||
|
|
||||||
Removes the PID file to release the lock, or raises an
|
|
||||||
error if the current process does not hold the lock.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
if not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
remove_existing_pidfile(self.path)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
""" Break an existing lock.
|
|
||||||
|
|
||||||
Removes the PID file if it already exists, otherwise does
|
|
||||||
nothing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
remove_existing_pidfile(self.path)
|
|
||||||
|
|
||||||
|
|
||||||
def read_pid_from_pidfile(pidfile_path):
|
|
||||||
""" Read the PID recorded in the named PID file.
|
|
||||||
|
|
||||||
Read and return the numeric PID recorded as text in the named
|
|
||||||
PID file. If the PID file cannot be read, or if the content is
|
|
||||||
not a valid PID, return ``None``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pid = None
|
|
||||||
try:
|
|
||||||
pidfile = open(pidfile_path, 'r')
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# According to the FHS 2.3 section on PID files in /var/run:
|
|
||||||
#
|
|
||||||
# The file must consist of the process identifier in
|
|
||||||
# ASCII-encoded decimal, followed by a newline character.
|
|
||||||
#
|
|
||||||
# Programs that read PID files should be somewhat flexible
|
|
||||||
# in what they accept; i.e., they should ignore extra
|
|
||||||
# whitespace, leading zeroes, absence of the trailing
|
|
||||||
# newline, or additional lines in the PID file.
|
|
||||||
|
|
||||||
line = pidfile.readline().strip()
|
|
||||||
try:
|
|
||||||
pid = int(line)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
pidfile.close()
|
|
||||||
|
|
||||||
return pid
|
|
||||||
|
|
||||||
|
|
||||||
def write_pid_to_pidfile(pidfile_path):
|
|
||||||
""" Write the PID in the named PID file.
|
|
||||||
|
|
||||||
Get the numeric process ID (“PID”) of the current process
|
|
||||||
and write it to the named file as a line of text.
|
|
||||||
|
|
||||||
"""
|
|
||||||
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
||||||
open_mode = 0o644
|
|
||||||
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
|
|
||||||
pidfile = os.fdopen(pidfile_fd, 'w')
|
|
||||||
|
|
||||||
# According to the FHS 2.3 section on PID files in /var/run:
|
|
||||||
#
|
|
||||||
# The file must consist of the process identifier in
|
|
||||||
# ASCII-encoded decimal, followed by a newline character. For
|
|
||||||
# example, if crond was process number 25, /var/run/crond.pid
|
|
||||||
# would contain three characters: two, five, and newline.
|
|
||||||
|
|
||||||
pid = os.getpid()
|
|
||||||
pidfile.write("%s\n" % pid)
|
|
||||||
pidfile.close()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_existing_pidfile(pidfile_path):
|
|
||||||
""" Remove the named PID file if it exists.
|
|
||||||
|
|
||||||
Removing a PID file that doesn't already exist puts us in the
|
|
||||||
desired state, so we ignore the condition if the file does not
|
|
||||||
exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
os.remove(pidfile_path)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.errno == errno.ENOENT:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
@ -1,156 +0,0 @@
|
|||||||
from __future__ import absolute_import, division
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
unicode
|
|
||||||
except NameError:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
|
|
||||||
|
|
||||||
|
|
||||||
class SQLiteLockFile(LockBase):
|
|
||||||
"Demonstrate SQL-based locking."
|
|
||||||
|
|
||||||
testdb = None
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = SQLiteLockFile('somefile')
|
|
||||||
>>> lock = SQLiteLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
self.lock_file = unicode(self.lock_file)
|
|
||||||
self.unique_name = unicode(self.unique_name)
|
|
||||||
|
|
||||||
if SQLiteLockFile.testdb is None:
|
|
||||||
import tempfile
|
|
||||||
_fd, testdb = tempfile.mkstemp()
|
|
||||||
os.close(_fd)
|
|
||||||
os.unlink(testdb)
|
|
||||||
del _fd, tempfile
|
|
||||||
SQLiteLockFile.testdb = testdb
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
self.connection = sqlite3.connect(SQLiteLockFile.testdb)
|
|
||||||
|
|
||||||
c = self.connection.cursor()
|
|
||||||
try:
|
|
||||||
c.execute("create table locks"
|
|
||||||
"("
|
|
||||||
" lock_file varchar(32),"
|
|
||||||
" unique_name varchar(32)"
|
|
||||||
")")
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.connection.commit()
|
|
||||||
import atexit
|
|
||||||
atexit.register(os.unlink, SQLiteLockFile.testdb)
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
wait = 0.1
|
|
||||||
elif timeout <= 0:
|
|
||||||
wait = 0
|
|
||||||
else:
|
|
||||||
wait = timeout / 10
|
|
||||||
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if not self.is_locked():
|
|
||||||
# Not locked. Try to lock it.
|
|
||||||
cursor.execute("insert into locks"
|
|
||||||
" (lock_file, unique_name)"
|
|
||||||
" values"
|
|
||||||
" (?, ?)",
|
|
||||||
(self.lock_file, self.unique_name))
|
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
# Check to see if we are the only lock holder.
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
if len(rows) > 1:
|
|
||||||
# Nope. Someone else got there. Remove our lock.
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
self.connection.commit()
|
|
||||||
else:
|
|
||||||
# Yup. We're done, so go home.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Check to see if we are the only lock holder.
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
if len(rows) == 1:
|
|
||||||
# We're the locker, so go home.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Maybe we should wait a bit longer.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
# No more waiting.
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
# Someone else has the lock and we are impatient..
|
|
||||||
raise AlreadyLocked("%s is already locked" % self.path)
|
|
||||||
|
|
||||||
# Well, okay. We'll give it a bit longer.
|
|
||||||
time.sleep(wait)
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
if not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me (by %s)" %
|
|
||||||
(self.unique_name, self._who_is_locking()))
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
def _who_is_locking(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select unique_name from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
return cursor.fetchone()[0]
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
return not not rows
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where lock_file = ?"
|
|
||||||
" and unique_name = ?",
|
|
||||||
(self.lock_file, self.unique_name))
|
|
||||||
return not not cursor.fetchall()
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
self.connection.commit()
|
|
@ -1,70 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from . import (LockBase, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class SymlinkLockFile(LockBase):
|
|
||||||
"""Lock access to a file using symlink(2)."""
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
# super(SymlinkLockFile).__init(...)
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
# split it back!
|
|
||||||
self.unique_name = os.path.split(self.unique_name)[1]
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
# Hopefully unnecessary for symlink.
|
|
||||||
# try:
|
|
||||||
# open(self.unique_name, "wb").close()
|
|
||||||
# except IOError:
|
|
||||||
# raise LockFailed("failed to create %s" % self.unique_name)
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Try and create a symbolic link to it.
|
|
||||||
try:
|
|
||||||
os.symlink(self.unique_name, self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
# Link creation failed. Maybe we've double-locked?
|
|
||||||
if self.i_am_locking():
|
|
||||||
# Linked to out unique name. Proceed.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Otherwise the lock creation failed.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout / 10 if timeout is not None else 0.1)
|
|
||||||
else:
|
|
||||||
# Link creation succeeded. We're good to go.
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.islink(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (os.path.islink(self.lock_file) and
|
|
||||||
os.readlink(self.lock_file) == self.unique_name)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.islink(self.lock_file): # exists && link
|
|
||||||
os.unlink(self.lock_file)
|
|
35
setup.cfg
35
setup.cfg
@ -1,35 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = lockfile
|
|
||||||
summary = Platform-independent file locking module
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = http://launchpad.net/pylockfile
|
|
||||||
classifier =
|
|
||||||
Intended Audience :: Developers
|
|
||||||
License :: OSI Approved :: MIT License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Operating System :: MacOS
|
|
||||||
Operating System :: Microsoft :: Windows :: Windows NT/2000
|
|
||||||
Operating System :: POSIX
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.3
|
|
||||||
Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages = lockfile
|
|
||||||
|
|
||||||
[pbr]
|
|
||||||
warnerrors = true
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
source-dir = doc/source
|
|
||||||
build-dir = doc/build
|
|
||||||
all_files = 1
|
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
universal=1
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=1.8'],
|
|
||||||
pbr=True)
|
|
@ -1,5 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
nose
|
|
||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
|
@ -1,262 +0,0 @@
|
|||||||
import os
|
|
||||||
import threading
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import lockfile
|
|
||||||
|
|
||||||
|
|
||||||
class ComplianceTest(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.saved_class = lockfile.LockFile
|
|
||||||
|
|
||||||
def _testfile(self):
|
|
||||||
"""Return platform-appropriate file. Helper for tests."""
|
|
||||||
import tempfile
|
|
||||||
return os.path.join(tempfile.gettempdir(), 'trash-%s' % os.getpid())
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
lockfile.LockFile = self.class_to_test
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
try:
|
|
||||||
tf = self._testfile()
|
|
||||||
if os.path.isdir(tf):
|
|
||||||
shutil.rmtree(tf)
|
|
||||||
elif os.path.isfile(tf):
|
|
||||||
os.unlink(tf)
|
|
||||||
elif not os.path.exists(tf):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise SystemError("unrecognized file: %s" % tf)
|
|
||||||
finally:
|
|
||||||
lockfile.LockFile = self.saved_class
|
|
||||||
|
|
||||||
def _test_acquire_helper(self, tbool):
|
|
||||||
# As simple as it gets.
|
|
||||||
lock = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
lock.acquire()
|
|
||||||
assert lock.i_am_locking()
|
|
||||||
lock.release()
|
|
||||||
assert not lock.is_locked()
|
|
||||||
|
|
||||||
# def test_acquire_basic_threaded(self):
|
|
||||||
# self._test_acquire_helper(True)
|
|
||||||
|
|
||||||
def test_acquire_basic_unthreaded(self):
|
|
||||||
self._test_acquire_helper(False)
|
|
||||||
|
|
||||||
def _test_acquire_no_timeout_helper(self, tbool):
|
|
||||||
# No timeout test
|
|
||||||
e1, e2 = threading.Event(), threading.Event()
|
|
||||||
t = _in_thread(self._lock_wait_unlock, e1, e2)
|
|
||||||
e1.wait() # wait for thread t to acquire lock
|
|
||||||
lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
assert lock2.is_locked()
|
|
||||||
if tbool:
|
|
||||||
assert not lock2.i_am_locking()
|
|
||||||
else:
|
|
||||||
assert lock2.i_am_locking()
|
|
||||||
|
|
||||||
try:
|
|
||||||
lock2.acquire(timeout=-1)
|
|
||||||
except lockfile.AlreadyLocked:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
lock2.release()
|
|
||||||
raise AssertionError("did not raise AlreadyLocked in"
|
|
||||||
" thread %s" %
|
|
||||||
threading.current_thread().get_name())
|
|
||||||
|
|
||||||
try:
|
|
||||||
lock2.acquire(timeout=0)
|
|
||||||
except lockfile.AlreadyLocked:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
lock2.release()
|
|
||||||
raise AssertionError("did not raise AlreadyLocked in"
|
|
||||||
" thread %s" %
|
|
||||||
threading.current_thread().get_name())
|
|
||||||
|
|
||||||
e2.set() # tell thread t to release lock
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
# def test_acquire_no_timeout_threaded(self):
|
|
||||||
# self._test_acquire_no_timeout_helper(True)
|
|
||||||
|
|
||||||
# def test_acquire_no_timeout_unthreaded(self):
|
|
||||||
# self._test_acquire_no_timeout_helper(False)
|
|
||||||
|
|
||||||
def _test_acquire_timeout_helper(self, tbool):
|
|
||||||
# Timeout test
|
|
||||||
e1, e2 = threading.Event(), threading.Event()
|
|
||||||
t = _in_thread(self._lock_wait_unlock, e1, e2)
|
|
||||||
e1.wait() # wait for thread t to acquire lock
|
|
||||||
lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
assert lock2.is_locked()
|
|
||||||
try:
|
|
||||||
lock2.acquire(timeout=0.1)
|
|
||||||
except lockfile.LockTimeout:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
lock2.release()
|
|
||||||
raise AssertionError("did not raise LockTimeout in thread %s" %
|
|
||||||
threading.current_thread().get_name())
|
|
||||||
|
|
||||||
e2.set()
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
def test_acquire_timeout_threaded(self):
|
|
||||||
self._test_acquire_timeout_helper(True)
|
|
||||||
|
|
||||||
def test_acquire_timeout_unthreaded(self):
|
|
||||||
self._test_acquire_timeout_helper(False)
|
|
||||||
|
|
||||||
def _test_context_timeout_helper(self, tbool):
|
|
||||||
# Timeout test
|
|
||||||
e1, e2 = threading.Event(), threading.Event()
|
|
||||||
t = _in_thread(self._lock_wait_unlock, e1, e2)
|
|
||||||
e1.wait() # wait for thread t to acquire lock
|
|
||||||
lock2 = lockfile.LockFile(self._testfile(), threaded=tbool,
|
|
||||||
timeout=0.2)
|
|
||||||
assert lock2.is_locked()
|
|
||||||
try:
|
|
||||||
lock2.acquire()
|
|
||||||
except lockfile.LockTimeout:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
lock2.release()
|
|
||||||
raise AssertionError("did not raise LockTimeout in thread %s" %
|
|
||||||
threading.current_thread().get_name())
|
|
||||||
|
|
||||||
e2.set()
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
def test_context_timeout_unthreaded(self):
|
|
||||||
self._test_context_timeout_helper(False)
|
|
||||||
|
|
||||||
def _test_release_basic_helper(self, tbool):
|
|
||||||
lock = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
lock.acquire()
|
|
||||||
assert lock.is_locked()
|
|
||||||
lock.release()
|
|
||||||
assert not lock.is_locked()
|
|
||||||
assert not lock.i_am_locking()
|
|
||||||
try:
|
|
||||||
lock.release()
|
|
||||||
except lockfile.NotLocked:
|
|
||||||
pass
|
|
||||||
except lockfile.NotMyLock:
|
|
||||||
raise AssertionError('unexpected exception: %s' %
|
|
||||||
lockfile.NotMyLock)
|
|
||||||
else:
|
|
||||||
raise AssertionError('erroneously unlocked file')
|
|
||||||
|
|
||||||
# def test_release_basic_threaded(self):
|
|
||||||
# self._test_release_basic_helper(True)
|
|
||||||
|
|
||||||
def test_release_basic_unthreaded(self):
|
|
||||||
self._test_release_basic_helper(False)
|
|
||||||
|
|
||||||
# def test_release_from_thread(self):
|
|
||||||
# e1, e2 = threading.Event(), threading.Event()
|
|
||||||
# t = _in_thread(self._lock_wait_unlock, e1, e2)
|
|
||||||
# e1.wait()
|
|
||||||
# lock2 = lockfile.LockFile(self._testfile(), threaded=False)
|
|
||||||
# assert not lock2.i_am_locking()
|
|
||||||
# try:
|
|
||||||
# lock2.release()
|
|
||||||
# except lockfile.NotMyLock:
|
|
||||||
# pass
|
|
||||||
# else:
|
|
||||||
# raise AssertionError('erroneously unlocked a file locked'
|
|
||||||
# ' by another thread.')
|
|
||||||
# e2.set()
|
|
||||||
# t.join()
|
|
||||||
|
|
||||||
def _test_is_locked_helper(self, tbool):
|
|
||||||
lock = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
lock.acquire(timeout=2)
|
|
||||||
assert lock.is_locked()
|
|
||||||
lock.release()
|
|
||||||
assert not lock.is_locked(), "still locked after release!"
|
|
||||||
|
|
||||||
# def test_is_locked_threaded(self):
|
|
||||||
# self._test_is_locked_helper(True)
|
|
||||||
|
|
||||||
def test_is_locked_unthreaded(self):
|
|
||||||
self._test_is_locked_helper(False)
|
|
||||||
|
|
||||||
# def test_i_am_locking_threaded(self):
|
|
||||||
# self._test_i_am_locking_helper(True)
|
|
||||||
|
|
||||||
def test_i_am_locking_unthreaded(self):
|
|
||||||
self._test_i_am_locking_helper(False)
|
|
||||||
|
|
||||||
def _test_i_am_locking_helper(self, tbool):
|
|
||||||
lock1 = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
assert not lock1.is_locked()
|
|
||||||
lock1.acquire()
|
|
||||||
try:
|
|
||||||
assert lock1.i_am_locking()
|
|
||||||
lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
assert lock2.is_locked()
|
|
||||||
if tbool:
|
|
||||||
assert not lock2.i_am_locking()
|
|
||||||
finally:
|
|
||||||
lock1.release()
|
|
||||||
|
|
||||||
def _test_break_lock_helper(self, tbool):
|
|
||||||
lock = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
lock.acquire()
|
|
||||||
assert lock.is_locked()
|
|
||||||
lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
|
|
||||||
assert lock2.is_locked()
|
|
||||||
lock2.break_lock()
|
|
||||||
assert not lock2.is_locked()
|
|
||||||
try:
|
|
||||||
lock.release()
|
|
||||||
except lockfile.NotLocked:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise AssertionError('break lock failed')
|
|
||||||
|
|
||||||
# def test_break_lock_threaded(self):
|
|
||||||
# self._test_break_lock_helper(True)
|
|
||||||
|
|
||||||
def test_break_lock_unthreaded(self):
|
|
||||||
self._test_break_lock_helper(False)
|
|
||||||
|
|
||||||
def _lock_wait_unlock(self, event1, event2):
|
|
||||||
"""Lock from another thread. Helper for tests."""
|
|
||||||
l = lockfile.LockFile(self._testfile())
|
|
||||||
l.acquire()
|
|
||||||
try:
|
|
||||||
event1.set() # we're in,
|
|
||||||
event2.wait() # wait for boss's permission to leave
|
|
||||||
finally:
|
|
||||||
l.release()
|
|
||||||
|
|
||||||
def test_enter(self):
|
|
||||||
lock = lockfile.LockFile(self._testfile())
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
assert lock.is_locked(), "Not locked after acquire!"
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
assert not lock.is_locked(), "still locked after release!"
|
|
||||||
|
|
||||||
def test_decorator(self):
|
|
||||||
@lockfile.locked(self._testfile())
|
|
||||||
def func(a, b):
|
|
||||||
return a + b
|
|
||||||
assert func(4, 3) == 7
|
|
||||||
|
|
||||||
|
|
||||||
def _in_thread(func, *args, **kwargs):
|
|
||||||
"""Execute func(*args, **kwargs) after dt seconds. Helper for tests."""
|
|
||||||
def _f():
|
|
||||||
func(*args, **kwargs)
|
|
||||||
t = threading.Thread(target=_f, name='/*/*')
|
|
||||||
t.setDaemon(True)
|
|
||||||
t.start()
|
|
||||||
return t
|
|
@ -1,41 +0,0 @@
|
|||||||
import lockfile.linklockfile
|
|
||||||
import lockfile.mkdirlockfile
|
|
||||||
import lockfile.pidlockfile
|
|
||||||
import lockfile.symlinklockfile
|
|
||||||
|
|
||||||
from compliancetest import ComplianceTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestLinkLockFile(ComplianceTest):
|
|
||||||
class_to_test = lockfile.linklockfile.LinkLockFile
|
|
||||||
|
|
||||||
|
|
||||||
class TestSymlinkLockFile(ComplianceTest):
|
|
||||||
class_to_test = lockfile.symlinklockfile.SymlinkLockFile
|
|
||||||
|
|
||||||
|
|
||||||
class TestMkdirLockFile(ComplianceTest):
|
|
||||||
class_to_test = lockfile.mkdirlockfile.MkdirLockFile
|
|
||||||
|
|
||||||
|
|
||||||
class TestPIDLockFile(ComplianceTest):
|
|
||||||
class_to_test = lockfile.pidlockfile.PIDLockFile
|
|
||||||
|
|
||||||
|
|
||||||
# Check backwards compatibility
|
|
||||||
class TestLinkFileLock(ComplianceTest):
|
|
||||||
class_to_test = lockfile.LinkFileLock
|
|
||||||
|
|
||||||
|
|
||||||
class TestMkdirFileLock(ComplianceTest):
|
|
||||||
class_to_test = lockfile.MkdirFileLock
|
|
||||||
|
|
||||||
try:
|
|
||||||
import sqlite3 # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
import lockfile.sqlitelockfile
|
|
||||||
|
|
||||||
class TestSQLiteLockFile(ComplianceTest):
|
|
||||||
class_to_test = lockfile.sqlitelockfile.SQLiteLockFile
|
|
27
tox.ini
27
tox.ini
@ -1,27 +0,0 @@
|
|||||||
# content of: tox.ini , put in same dir as setup.py
|
|
||||||
[tox]
|
|
||||||
envlist = py27,py34
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
|
||||||
commands=nosetests
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
deps = flake8
|
|
||||||
commands = flake8
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands = python setup.py build_sphinx
|
|
||||||
|
|
||||||
[testenv:cover]
|
|
||||||
deps = {[testenv]deps}
|
|
||||||
coverage
|
|
||||||
commands =
|
|
||||||
nosetests --with-coverage --cover-erase --cover-package=lockfile --cover-inclusive []
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
exclude=.venv,.git,.tox,dist,doc
|
|
||||||
show-source = True
|
|
Loading…
Reference in New Issue
Block a user