~bzr-pqm/bzr/bzr.dev

3474.1.2 by Martin Pool
CountedLock.unlock should raise LockNotHeld if appropriate
1
# Copyright (C) 2007, 2008 Canonical Ltd
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Counted lock class"""
18
19
3474.1.2 by Martin Pool
CountedLock.unlock should raise LockNotHeld if appropriate
20
from bzrlib import (
21
    errors,
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
22
    )
23
24
25
class CountedLock(object):
26
    """Decorator around a lock that makes it reentrant.
27
28
    This can be used with any object that provides a basic Lock interface,
29
    including LockDirs and OS file locks.
30
    """
31
32
    def __init__(self, real_lock):
33
        self._real_lock = real_lock
34
        self._lock_mode = None
35
        self._lock_count = 0
36
3474.1.2 by Martin Pool
CountedLock.unlock should raise LockNotHeld if appropriate
37
    def __repr__(self):
38
        return "%s(%r)" % (self.__class__.__name__,
39
            self._real_lock)
40
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
41
    def break_lock(self):
42
        self._real_lock.break_lock()
43
        self._lock_mode = None
44
        self._lock_count = 0
45
46
    def is_locked(self):
47
        return self._lock_mode is not None
48
49
    def lock_read(self):
50
        """Acquire the lock in read mode.
51
52
        If the lock is already held in either read or write mode this
53
        increments the count and succeeds.  If the lock is not already held,
54
        it is taken in read mode.
55
        """
56
        if self._lock_mode:
57
            self._lock_count += 1
58
        else:
59
            self._real_lock.lock_read()
60
            self._lock_count = 1
61
            self._lock_mode = 'r'
62
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
63
    def lock_write(self, token=None):
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
64
        """Acquire the lock in write mode.
65
66
        If the lock was originally acquired in read mode this will fail.
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
67
68
        :param token: If non-None, reacquire the lock using this token.
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
69
        """
70
        if self._lock_count == 0:
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
71
            return_token = self._real_lock.lock_write(token)
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
72
            self._lock_mode = 'w'
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
73
            self._lock_count += 1
74
            return return_token
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
75
        elif self._lock_mode != 'w':
3474.1.2 by Martin Pool
CountedLock.unlock should raise LockNotHeld if appropriate
76
            raise errors.ReadOnlyError(self)
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
77
        else:
78
            self._real_lock.validate_token(token)
79
            self._lock_count += 1
80
            return token
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
81
82
    def unlock(self):
83
        if self._lock_count == 0:
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
84
            raise errors.LockNotHeld(self)
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
85
        elif self._lock_count == 1:
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
86
            # these are decremented first; if we fail to unlock the most
87
            # reasonable assumption is that we still don't have the lock
88
            # anymore
89
            self._lock_mode = None
90
            self._lock_count -= 1
2475.4.1 by Martin Pool
Start adding CountedLock class to partially replace LockableFiles
91
            self._real_lock.unlock()
3474.1.3 by Martin Pool
CountedLock now handles and tests lock tokens
92
        else:
93
            self._lock_count -= 1