Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/lockfile/pidlockfile.py: 25%

68 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

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 

15from __future__ import absolute_import 

16 

17import errno 

18import os 

19import time 

20 

21from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, 

22 LockTimeout) 

23 

24 

25class PIDLockFile(LockBase): 

26 """ Lockfile implemented as a Unix PID file. 

27 

28 The lock file is a normal file named by the attribute `path`. 

29 A lock's PID file contains a single line of text, containing 

30 the process ID (PID) of the process that acquired the lock. 

31 

32 >>> lock = PIDLockFile('somefile') 

33 >>> lock = PIDLockFile('somefile') 

34 """ 

35 

36 def __init__(self, path, threaded=False, timeout=None): 

37 # pid lockfiles don't support threaded operation, so always force 

38 # False as the threaded arg. 

39 LockBase.__init__(self, path, False, timeout) 

40 self.unique_name = self.path 

41 

42 def read_pid(self): 

43 """ Get the PID from the lock file. 

44 """ 

45 return read_pid_from_pidfile(self.path) 

46 

47 def is_locked(self): 

48 """ Test if the lock is currently held. 

49 

50 The lock is held if the PID file for this lock exists. 

51 

52 """ 

53 return os.path.exists(self.path) 

54 

55 def i_am_locking(self): 

56 """ Test if the lock is held by the current process. 

57 

58 Returns ``True`` if the current process ID matches the 

59 number stored in the PID file. 

60 """ 

61 return self.is_locked() and os.getpid() == self.read_pid() 

62 

63 def acquire(self, timeout=None): 

64 """ Acquire the lock. 

65 

66 Creates the PID file for this lock, or raises an error if 

67 the lock could not be acquired. 

68 """ 

69 

70 timeout = timeout if timeout is not None else self.timeout 

71 end_time = time.time() 

72 if timeout is not None and timeout > 0: 

73 end_time += timeout 

74 

75 while True: 

76 try: 

77 write_pid_to_pidfile(self.path) 

78 except OSError as exc: 

79 if exc.errno == errno.EEXIST: 

80 # The lock creation failed. Maybe sleep a bit. 

81 if time.time() > end_time: 

82 if timeout is not None and timeout > 0: 

83 raise LockTimeout("Timeout waiting to acquire" 

84 " lock for %s" % 

85 self.path) 

86 else: 

87 raise AlreadyLocked("%s is already locked" % 

88 self.path) 

89 time.sleep(timeout is not None and timeout / 10 or 0.1) 

90 else: 

91 raise LockFailed("failed to create %s" % self.path) 

92 else: 

93 return 

94 

95 def release(self): 

96 """ Release the lock. 

97 

98 Removes the PID file to release the lock, or raises an 

99 error if the current process does not hold the lock. 

100 

101 """ 

102 if not self.is_locked(): 

103 raise NotLocked("%s is not locked" % self.path) 

104 if not self.i_am_locking(): 

105 raise NotMyLock("%s is locked, but not by me" % self.path) 

106 remove_existing_pidfile(self.path) 

107 

108 def break_lock(self): 

109 """ Break an existing lock. 

110 

111 Removes the PID file if it already exists, otherwise does 

112 nothing. 

113 

114 """ 

115 remove_existing_pidfile(self.path) 

116 

117 

118def read_pid_from_pidfile(pidfile_path): 

119 """ Read the PID recorded in the named PID file. 

120 

121 Read and return the numeric PID recorded as text in the named 

122 PID file. If the PID file cannot be read, or if the content is 

123 not a valid PID, return ``None``. 

124 

125 """ 

126 pid = None 

127 try: 

128 pidfile = open(pidfile_path, 'r') 

129 except IOError: 

130 pass 

131 else: 

132 # According to the FHS 2.3 section on PID files in /var/run: 

133 # 

134 # The file must consist of the process identifier in 

135 # ASCII-encoded decimal, followed by a newline character. 

136 # 

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. 

141 

142 line = pidfile.readline().strip() 

143 try: 

144 pid = int(line) 

145 except ValueError: 

146 pass 

147 pidfile.close() 

148 

149 return pid 

150 

151 

152def write_pid_to_pidfile(pidfile_path): 

153 """ Write the PID in the named PID file. 

154 

155 Get the numeric process ID (“PID”) of the current process 

156 and write it to the named file as a line of text. 

157 

158 """ 

159 open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY) 

160 open_mode = 0o644 

161 pidfile_fd = os.open(pidfile_path, open_flags, open_mode) 

162 pidfile = os.fdopen(pidfile_fd, 'w') 

163 

164 # According to the FHS 2.3 section on PID files in /var/run: 

165 # 

166 # The file must consist of the process identifier in 

167 # ASCII-encoded decimal, followed by a newline character. For 

168 # example, if crond was process number 25, /var/run/crond.pid 

169 # would contain three characters: two, five, and newline. 

170 

171 pid = os.getpid() 

172 pidfile.write("%s\n" % pid) 

173 pidfile.close() 

174 

175 

176def remove_existing_pidfile(pidfile_path): 

177 """ Remove the named PID file if it exists. 

178 

179 Removing a PID file that doesn't already exist puts us in the 

180 desired state, so we ignore the condition if the file does not 

181 exist. 

182 

183 """ 

184 try: 

185 os.remove(pidfile_path) 

186 except OSError as exc: 

187 if exc.errno == errno.ENOENT: 

188 pass 

189 else: 

190 raise