Statistics
| Branch: | Tag: | Revision:

root / lib / utils / filelock.py @ adc523ab

History | View | Annotate | Download (5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Utility functions for file-based locks.
22

23
"""
24

    
25
import fcntl
26
import errno
27
import os
28
import logging
29

    
30
from ganeti import errors
31
from ganeti.utils import retry
32

    
33

    
34
def LockFile(fd):
35
  """Locks a file using POSIX locks.
36

37
  @type fd: int
38
  @param fd: the file descriptor we need to lock
39

40
  """
41
  try:
42
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
43
  except IOError, err:
44
    if err.errno == errno.EAGAIN:
45
      raise errors.LockError("File already locked")
46
    raise
47

    
48

    
49
class FileLock(object):
50
  """Utility class for file locks.
51

52
  """
53
  def __init__(self, fd, filename):
54
    """Constructor for FileLock.
55

56
    @type fd: file
57
    @param fd: File object
58
    @type filename: str
59
    @param filename: Path of the file opened at I{fd}
60

61
    """
62
    self.fd = fd
63
    self.filename = filename
64

    
65
  @classmethod
66
  def Open(cls, filename):
67
    """Creates and opens a file to be used as a file-based lock.
68

69
    @type filename: string
70
    @param filename: path to the file to be locked
71

72
    """
73
    # Using "os.open" is necessary to allow both opening existing file
74
    # read/write and creating if not existing. Vanilla "open" will truncate an
75
    # existing file -or- allow creating if not existing.
76
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
77
               filename)
78

    
79
  def __del__(self):
80
    self.Close()
81

    
82
  def Close(self):
83
    """Close the file and release the lock.
84

85
    """
86
    if hasattr(self, "fd") and self.fd:
87
      self.fd.close()
88
      self.fd = None
89

    
90
  def _flock(self, flag, blocking, timeout, errmsg):
91
    """Wrapper for fcntl.flock.
92

93
    @type flag: int
94
    @param flag: operation flag
95
    @type blocking: bool
96
    @param blocking: whether the operation should be done in blocking mode.
97
    @type timeout: None or float
98
    @param timeout: for how long the operation should be retried (implies
99
                    non-blocking mode).
100
    @type errmsg: string
101
    @param errmsg: error message in case operation fails.
102

103
    """
104
    assert self.fd, "Lock was closed"
105
    assert timeout is None or timeout >= 0, \
106
      "If specified, timeout must be positive"
107
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
108

    
109
    # When a timeout is used, LOCK_NB must always be set
110
    if not (timeout is None and blocking):
111
      flag |= fcntl.LOCK_NB
112

    
113
    if timeout is None:
114
      self._Lock(self.fd, flag, timeout)
115
    else:
116
      try:
117
        retry.Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
118
                    args=(self.fd, flag, timeout))
119
      except retry.RetryTimeout:
120
        raise errors.LockError(errmsg)
121

    
122
  @staticmethod
123
  def _Lock(fd, flag, timeout):
124
    try:
125
      fcntl.flock(fd, flag)
126
    except IOError, err:
127
      if timeout is not None and err.errno == errno.EAGAIN:
128
        raise retry.RetryAgain()
129

    
130
      logging.exception("fcntl.flock failed")
131
      raise
132

    
133
  def Exclusive(self, blocking=False, timeout=None):
134
    """Locks the file in exclusive mode.
135

136
    @type blocking: boolean
137
    @param blocking: whether to block and wait until we
138
        can lock the file or return immediately
139
    @type timeout: int or None
140
    @param timeout: if not None, the duration to wait for the lock
141
        (in blocking mode)
142

143
    """
144
    self._flock(fcntl.LOCK_EX, blocking, timeout,
145
                "Failed to lock %s in exclusive mode" % self.filename)
146

    
147
  def Shared(self, blocking=False, timeout=None):
148
    """Locks the file in shared mode.
149

150
    @type blocking: boolean
151
    @param blocking: whether to block and wait until we
152
        can lock the file or return immediately
153
    @type timeout: int or None
154
    @param timeout: if not None, the duration to wait for the lock
155
        (in blocking mode)
156

157
    """
158
    self._flock(fcntl.LOCK_SH, blocking, timeout,
159
                "Failed to lock %s in shared mode" % self.filename)
160

    
161
  def Unlock(self, blocking=True, timeout=None):
162
    """Unlocks the file.
163

164
    According to C{flock(2)}, unlocking can also be a nonblocking
165
    operation::
166

167
      To make a non-blocking request, include LOCK_NB with any of the above
168
      operations.
169

170
    @type blocking: boolean
171
    @param blocking: whether to block and wait until we
172
        can lock the file or return immediately
173
    @type timeout: int or None
174
    @param timeout: if not None, the duration to wait for the lock
175
        (in blocking mode)
176

177
    """
178
    self._flock(fcntl.LOCK_UN, blocking, timeout,
179
                "Failed to unlock %s" % self.filename)