Coverage for drivers/flock.py : 65%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# Copyright (C) Citrix Systems Inc.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published
6# by the Free Software Foundation; version 2.1 only.
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 Lesser General Public License for more details.
12#
13# You should have received a copy of the GNU Lesser General Public License
14# along with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16#
18"""
19Fcntl-based Advisory Locking with a proper .trylock()
21Python's fcntl module is not good at locking. In particular, proper
22testing and trying of locks isn't well supported. Looks as if we've
23got to grow our own.
24"""
26from sm_typing import ClassVar, override
28import os
29import fcntl
30import struct
31import errno
34class Flock:
35 """A C flock struct."""
37 def __init__(self, l_type, l_whence=0, l_start=0, l_len=0, l_pid=0):
38 """See fcntl(2) for field details."""
39 self.fields = [l_type, l_whence, l_start, l_len, l_pid]
41 FORMAT = "hhqql"
42 # struct flock(2) format, tested with python2.4/i686 and
43 # python2.5/x86_64. http://docs.python.org/lib/posix-large-files.html
45 def fcntl(self, fd, cmd):
46 """Issues a system fcntl(fd, cmd, self). Updates self with what was
47 returned by the kernel. Otherwise raises IOError(errno)."""
49 st = struct.pack(self.FORMAT, * self.fields)
50 st = fcntl.fcntl(fd, cmd, st)
52 fields = struct.unpack(self.FORMAT, st)
53 self.__init__( * fields)
55 FIELDS = {'l_type': 0,
56 'l_whence': 1,
57 'l_start': 2,
58 'l_len': 3,
59 'l_pid': 4}
61 def __getattr__(self, name):
62 idx = self.FIELDS[name]
63 return self.fields[idx]
65 @override
66 def __setattr__(self, name, value) -> None:
67 idx = self.FIELDS.get(name)
68 if idx is None: 68 ↛ 71line 68 didn't jump to line 71, because the condition on line 68 was never false
69 self.__dict__[name] = value
70 else:
71 self.fields[idx] = value
74class FcntlLockBase:
75 """Abstract base class for either reader or writer locks. A respective
76 definition of LOCK_TYPE (fcntl.{F_RDLCK|F_WRLCK}) determines the
77 type."""
79 LOCK_TYPE: ClassVar[int]
81 if __debug__:
82 ERROR_ISLOCKED = "Attempt to acquire lock held."
83 ERROR_NOTLOCKED = "Attempt to unlock lock not held."
85 def __init__(self, fd):
86 """Creates a new, unheld lock."""
87 self.fd = fd
88 #
89 # Subtle: fcntl(2) permits re-locking it as often as you want
90 # once you hold it. This is slightly counterintuitive and we
91 # want clean code, so we add one bit of our own bookkeeping.
92 #
93 self._held = False
95 def lock(self):
96 """Blocking lock aquisition."""
97 assert not self._held, self.ERROR_ISLOCKED
98 Flock(self.LOCK_TYPE).fcntl(self.fd, fcntl.F_SETLKW)
99 self._held = True
101 def trylock(self):
102 """Non-blocking lock aquisition. Returns True on success, False
103 otherwise."""
104 if self._held: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true
105 return False
106 try:
107 Flock(self.LOCK_TYPE).fcntl(self.fd, fcntl.F_SETLK)
108 except IOError as e:
109 if e.errno in [errno.EACCES, errno.EAGAIN]:
110 return False
111 raise
112 self._held = True
113 return True
115 def held(self):
116 """Returns True if @self holds the lock, False otherwise."""
117 return self._held
119 def unlock(self):
120 """Release a previously acquired lock."""
121 Flock(fcntl.F_UNLCK).fcntl(self.fd, fcntl.F_SETLK)
122 self._held = False
124 def test(self):
125 """Returns the PID of the process holding the lock or -1 if the lock
126 is not held."""
127 if self._held:
128 return os.getpid()
129 flock = Flock(self.LOCK_TYPE)
130 flock.fcntl(self.fd, fcntl.F_GETLK)
131 if flock.l_type == fcntl.F_UNLCK:
132 return -1
133 return flock.l_pid
136class WriteLock(FcntlLockBase):
137 """A simple global writer (i.e. exclusive) lock."""
138 LOCK_TYPE = fcntl.F_WRLCK
141class ReadLock(FcntlLockBase):
142 """A simple global reader (i.e. shared) lock."""
143 LOCK_TYPE = fcntl.F_RDLCK