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
|
||||
using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
|
||||
used instead.
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
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 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.
|
||||
For an alternative project, please see `fasteners`_ or `oslo.concurrency`
|
||||
|
||||
The lock mechanism relies on the atomic nature of the link (on UNIX) and
|
||||
``mkdir`` (on Windows) system calls. An implementation based on SQLite is also
|
||||
provided, more as a demonstration of the possibilities it provides than as
|
||||
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.
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
||||
.. _fasteners: https://pypi.python.org/pypi/fasteners
|
||||
.. _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