1
# -*- coding: utf-8 -*-
3
# daemon/pidlockfile.py
4
# Part of python-daemon, an implementation of PEP 3143.
6
# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
8
# This is free software: you may copy, modify, and/or distribute this work
9
# under the terms of the Python Software Foundation License, version 2 or
10
# later as published by the Python Software Foundation.
11
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
14
""" Lockfile behaviour implemented via Unix PID files.
20
from lockfile import (
22
AlreadyLocked, LockFailed,
27
class PIDFileError(Exception):
28
""" Abstract base class for errors specific to PID files. """
30
class PIDFileParseError(ValueError, PIDFileError):
31
""" Raised when parsing contents of PID file fails. """
34
class PIDLockFile(FileLock, object):
35
""" Lockfile implemented as a Unix PID file.
37
The PID file is named by the attribute `path`. When locked,
38
the file will be created with a single line of text,
39
containing the process ID (PID) of the process that acquired
42
The lock is acquired and maintained as per `LinkFileLock`.
47
""" Get the PID from the lock file.
49
result = read_pid_from_pidfile(self.path)
52
def acquire(self, *args, **kwargs):
55
Locks the PID file then creates the PID file for this
56
lock. The `timeout` parameter is used as for the
60
super(PIDLockFile, self).acquire(*args, **kwargs)
62
write_pid_to_pidfile(self.path)
64
error = LockFailed("%(exc)s" % vars())
70
Removes the PID file then releases the lock, or raises an
71
error if the current process does not hold the lock.
74
if self.i_am_locking():
75
remove_existing_pidfile(self.path)
76
super(PIDLockFile, self).release()
79
""" Break an existing lock.
81
If the lock is held, breaks the lock and removes the PID
85
super(PIDLockFile, self).break_lock()
86
remove_existing_pidfile(self.path)
89
class TimeoutPIDLockFile(PIDLockFile):
90
""" Lockfile with default timeout, implemented as a Unix PID file.
92
This uses the ``PIDLockFile`` implementation, with the
95
* The `acquire_timeout` parameter to the initialiser will be
96
used as the default `timeout` parameter for the `acquire`
101
def __init__(self, path, acquire_timeout=None, *args, **kwargs):
102
""" Set up the parameters of a DaemonRunnerLock. """
103
self.acquire_timeout = acquire_timeout
104
super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
106
def acquire(self, timeout=None, *args, **kwargs):
107
""" Acquire the lock. """
109
timeout = self.acquire_timeout
110
super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
113
def read_pid_from_pidfile(pidfile_path):
114
""" Read the PID recorded in the named PID file.
116
Read and return the numeric PID recorded as text in the named
117
PID file. If the PID file does not exist, return ``None``. If
118
the content is not a valid PID, raise ``PIDFileParseError``.
124
pidfile = open(pidfile_path, 'r')
126
if exc.errno == errno.ENOENT:
132
# According to the FHS 2.3 section on PID files in ‘/var/run’:
134
# The file must consist of the process identifier in
135
# ASCII-encoded decimal, followed by a newline character. …
137
# Programs that read PID files should be somewhat flexible
138
# in what they accept; i.e., they should ignore extra
139
# whitespace, leading zeroes, absence of the trailing
140
# newline, or additional lines in the PID file.
142
line = pidfile.readline().strip()
146
raise PIDFileParseError(
147
"PID file %(pidfile_path)r contents invalid" % vars())
153
def write_pid_to_pidfile(pidfile_path):
154
""" Write the PID in the named PID file.
156
Get the numeric process ID (“PID”) of the current process
157
and write it to the named file as a line of text.
160
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
162
((os.R_OK | os.W_OK) << 6) |
165
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
166
pidfile = os.fdopen(pidfile_fd, 'w')
168
# According to the FHS 2.3 section on PID files in ‘/var/run’:
170
# The file must consist of the process identifier in
171
# ASCII-encoded decimal, followed by a newline character. For
172
# example, if crond was process number 25, /var/run/crond.pid
173
# would contain three characters: two, five, and newline.
176
line = "%(pid)d\n" % vars()
181
def remove_existing_pidfile(pidfile_path):
182
""" Remove the named PID file if it exists.
184
Remove the named PID file. Ignore the condition if the file
185
does not exist, since that only means we are already in the
190
os.remove(pidfile_path)
192
if exc.errno == errno.ENOENT: