1
# -*- coding: utf-8 -*-
5
# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
7
# This is free software: you may copy, modify, and/or distribute this work
8
# under the terms of the Python Software Foundation License, version 2 or
9
# later as published by the Python Software Foundation.
10
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
12
""" Lockfile behaviour implemented via Unix PID files.
15
from __future__ import absolute_import
22
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
26
class PIDLockFile(LockBase):
27
""" Lockfile implemented as a Unix PID file.
29
The lock file is a normal file named by the attribute `path`.
30
A lock's PID file contains a single line of text, containing
31
the process ID (PID) of the process that acquired the lock.
33
>>> lock = PIDLockFile('somefile')
34
>>> lock = PIDLockFile('somefile', threaded=False)
38
""" Get the PID from the lock file.
40
return read_pid_from_pidfile(self.path)
43
""" Test if the lock is currently held.
45
The lock is held if the PID file for this lock exists.
48
return os.path.exists(self.path)
50
def i_am_locking(self):
51
""" Test if the lock is held by the current process.
53
Returns ``True`` if the current process ID matches the
54
number stored in the PID file.
56
return self.is_locked() and os.getpid() == self.read_pid()
58
def acquire(self, timeout=None):
61
Creates the PID file for this lock, or raises an error if
62
the lock could not be acquired.
65
end_time = time.time()
66
if timeout is not None and timeout > 0:
71
write_pid_to_pidfile(self.path)
73
if exc.errno == errno.EEXIST:
74
# The lock creation failed. Maybe sleep a bit.
75
if timeout is not None and time.time() > end_time:
80
time.sleep(timeout is not None and timeout/10 or 0.1)
89
Removes the PID file to release the lock, or raises an
90
error if the current process does not hold the lock.
93
if not self.is_locked():
95
if not self.i_am_locking():
97
remove_existing_pidfile(self.path)
100
""" Break an existing lock.
102
Removes the PID file if it already exists, otherwise does
106
remove_existing_pidfile(self.path)
108
def read_pid_from_pidfile(pidfile_path):
109
""" Read the PID recorded in the named PID file.
111
Read and return the numeric PID recorded as text in the named
112
PID file. If the PID file cannot be read, or if the content is
113
not a valid PID, return ``None``.
118
pidfile = open(pidfile_path, 'r')
122
# According to the FHS 2.3 section on PID files in /var/run:
124
# The file must consist of the process identifier in
125
# ASCII-encoded decimal, followed by a newline character.
127
# Programs that read PID files should be somewhat flexible
128
# in what they accept; i.e., they should ignore extra
129
# whitespace, leading zeroes, absence of the trailing
130
# newline, or additional lines in the PID file.
132
line = pidfile.readline().strip()
142
def write_pid_to_pidfile(pidfile_path):
143
""" Write the PID in the named PID file.
145
Get the numeric process ID (“PID”) of the current process
146
and write it to the named file as a line of text.
149
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
151
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
152
pidfile = os.fdopen(pidfile_fd, 'w')
154
# According to the FHS 2.3 section on PID files in /var/run:
156
# The file must consist of the process identifier in
157
# ASCII-encoded decimal, followed by a newline character. For
158
# example, if crond was process number 25, /var/run/crond.pid
159
# would contain three characters: two, five, and newline.
162
line = "%(pid)d\n" % vars()
167
def remove_existing_pidfile(pidfile_path):
168
""" Remove the named PID file if it exists.
170
Removing a PID file that doesn't already exist puts us in the
171
desired state, so we ignore the condition if the file does not
176
os.remove(pidfile_path)
178
if exc.errno == errno.ENOENT: