~0x44/nova/bug838466

« back to all changes in this revision

Viewing changes to vendor/lockfile/lockfile/pidlockfile.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
 
 
3
# pidlockfile.py
 
4
#
 
5
# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
 
6
#
 
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.
 
11
 
 
12
""" Lockfile behaviour implemented via Unix PID files.
 
13
    """
 
14
 
 
15
from __future__ import absolute_import
 
16
 
 
17
import os
 
18
import sys
 
19
import errno
 
20
import time
 
21
 
 
22
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
 
23
               LockTimeout)
 
24
 
 
25
 
 
26
class PIDLockFile(LockBase):
 
27
    """ Lockfile implemented as a Unix PID file.
 
28
 
 
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.
 
32
 
 
33
    >>> lock = PIDLockFile('somefile')
 
34
    >>> lock = PIDLockFile('somefile', threaded=False)
 
35
    """
 
36
 
 
37
    def read_pid(self):
 
38
        """ Get the PID from the lock file.
 
39
            """
 
40
        return read_pid_from_pidfile(self.path)
 
41
 
 
42
    def is_locked(self):
 
43
        """ Test if the lock is currently held.
 
44
 
 
45
            The lock is held if the PID file for this lock exists.
 
46
 
 
47
            """
 
48
        return os.path.exists(self.path)
 
49
 
 
50
    def i_am_locking(self):
 
51
        """ Test if the lock is held by the current process.
 
52
 
 
53
        Returns ``True`` if the current process ID matches the
 
54
        number stored in the PID file.
 
55
        """
 
56
        return self.is_locked() and os.getpid() == self.read_pid()
 
57
 
 
58
    def acquire(self, timeout=None):
 
59
        """ Acquire the lock.
 
60
 
 
61
        Creates the PID file for this lock, or raises an error if
 
62
        the lock could not be acquired.
 
63
        """
 
64
 
 
65
        end_time = time.time()
 
66
        if timeout is not None and timeout > 0:
 
67
            end_time += timeout
 
68
 
 
69
        while True:
 
70
            try:
 
71
                write_pid_to_pidfile(self.path)
 
72
            except OSError, exc:
 
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:
 
76
                        if timeout > 0:
 
77
                            raise LockTimeout
 
78
                        else:
 
79
                            raise AlreadyLocked
 
80
                    time.sleep(timeout is not None and timeout/10 or 0.1)
 
81
                else:
 
82
                    raise LockFailed
 
83
            else:
 
84
                return
 
85
 
 
86
    def release(self):
 
87
        """ Release the lock.
 
88
 
 
89
            Removes the PID file to release the lock, or raises an
 
90
            error if the current process does not hold the lock.
 
91
 
 
92
            """
 
93
        if not self.is_locked():
 
94
            raise NotLocked
 
95
        if not self.i_am_locking():
 
96
            raise NotMyLock
 
97
        remove_existing_pidfile(self.path)
 
98
 
 
99
    def break_lock(self):
 
100
        """ Break an existing lock.
 
101
 
 
102
            Removes the PID file if it already exists, otherwise does
 
103
            nothing.
 
104
 
 
105
            """
 
106
        remove_existing_pidfile(self.path)
 
107
 
 
108
def read_pid_from_pidfile(pidfile_path):
 
109
    """ Read the PID recorded in the named PID file.
 
110
 
 
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``.
 
114
 
 
115
        """
 
116
    pid = None
 
117
    try:
 
118
        pidfile = open(pidfile_path, 'r')
 
119
    except IOError:
 
120
        pass
 
121
    else:
 
122
        # According to the FHS 2.3 section on PID files in /var/run:
 
123
        # 
 
124
        #   The file must consist of the process identifier in
 
125
        #   ASCII-encoded decimal, followed by a newline character.
 
126
        # 
 
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.
 
131
 
 
132
        line = pidfile.readline().strip()
 
133
        try:
 
134
            pid = int(line)
 
135
        except ValueError:
 
136
            pass
 
137
        pidfile.close()
 
138
 
 
139
    return pid
 
140
 
 
141
 
 
142
def write_pid_to_pidfile(pidfile_path):
 
143
    """ Write the PID in the named PID file.
 
144
 
 
145
        Get the numeric process ID (“PID”) of the current process
 
146
        and write it to the named file as a line of text.
 
147
 
 
148
        """
 
149
    open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
 
150
    open_mode = 0x644
 
151
    pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
 
152
    pidfile = os.fdopen(pidfile_fd, 'w')
 
153
 
 
154
    # According to the FHS 2.3 section on PID files in /var/run:
 
155
    #
 
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.
 
160
 
 
161
    pid = os.getpid()
 
162
    line = "%(pid)d\n" % vars()
 
163
    pidfile.write(line)
 
164
    pidfile.close()
 
165
 
 
166
 
 
167
def remove_existing_pidfile(pidfile_path):
 
168
    """ Remove the named PID file if it exists.
 
169
 
 
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
 
172
        exist.
 
173
 
 
174
        """
 
175
    try:
 
176
        os.remove(pidfile_path)
 
177
    except OSError, exc:
 
178
        if exc.errno == errno.ENOENT:
 
179
            pass
 
180
        else:
 
181
            raise