Skip to content

Commit 1cb5c99

Browse files
committed
Address potential race conditions.
1 parent ee76f52 commit 1cb5c99

File tree

1 file changed

+32
-11
lines changed

1 file changed

+32
-11
lines changed

convert2rhel/applock.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,18 @@ def _read_pidfile(self):
115115
:raises: ApplicationLockedError if the contents are corrupt
116116
"""
117117
if os.path.exists(self._pidfile):
118-
with open(self._pidfile, "r") as f:
119-
file_contents = f.read()
120118
try:
119+
with open(self._pidfile, "r") as f:
120+
file_contents = f.read()
121121
pid = int(file_contents.rstrip())
122+
except OSError as exc:
123+
# This addresses a race condition in which another process
124+
# deletes the lock file between our check that it exists
125+
# and our attempt to read the contents.
126+
# In Python 3 this could be changed to FileNotFoundError.
127+
if exc.errno == errno.ENOENT:
128+
return None
129+
raise exc
122130
except ValueError:
123131
raise ApplicationLockedError("Lock file %s is corrupt" % self._pidfile)
124132
return pid
@@ -138,10 +146,23 @@ def _pid_exists(pid):
138146
return False
139147
return True
140148

149+
def _safe_unlink(self):
150+
"""Unlink the lock file. If the unlink fails because the file
151+
doesn't exist, swallow the exception; this avoids spurious
152+
errors due to race conditions.
153+
"""
154+
try:
155+
os.unlink(self._pidfile)
156+
except OSError as exc:
157+
# In Python 3 this could be changed to FileNotFoundError.
158+
if exc.errno == errno.ENOENT:
159+
return
160+
raise exc
161+
141162
def try_to_lock(self):
142-
"""Try to get a lock on this application. If successful,
143-
the application will be locked; the lock should be released
144-
with unlock().
163+
"""Try to get a lock on this application. If this method does
164+
not raise an Exception, the application will be locked and we
165+
hold the lock; the lock should be released with unlock().
145166
146167
If the file has unexpected contents, for safety we treat the
147168
application as locked, since it is probably the result of
@@ -159,22 +180,22 @@ def try_to_lock(self):
159180
# The lock file was created by a process that has exited;
160181
# remove it and try again.
161182
loggerinst.info("Cleaning up lock held by exited process %d." % pid)
162-
os.unlink(self._pidfile)
183+
self._safe_unlink()
163184
if not self._try_create():
164-
# Race condition: between the unlink and our attempt to
165-
# create the lock file, another process got there first.
185+
# Between the unlink and our attempt to create the lock
186+
# file, another process got there first.
166187
raise ApplicationLockedError("%s is locked" % self._pidfile)
167188

168189
def unlock(self):
169190
"""Release the lock on this application.
170191
171-
Note that if the unlink fails (a pathological failure) the
172-
object will stay locked and the OSError or other
192+
Note that if the safe unlink fails (a pathological failure)
193+
the object will stay locked and the OSError or other
173194
system-generated exception will be raised.
174195
"""
175196
if not self.is_locked:
176197
return
177-
os.unlink(self._pidfile)
198+
self._safe_unlink()
178199
loggerinst.debug("%s." % self)
179200

180201
def __enter__(self):

0 commit comments

Comments
 (0)