@@ -119,6 +119,10 @@ def _lock_file_path(self):
119
119
"""
120
120
return "%s.lock" % (self ._file_path )
121
121
122
+ def _get_id (self ):
123
+ """Returns string id to be written into the lock file"""
124
+ return "%i|%i" % (os .getpid (), hash (self ))
125
+
122
126
def _has_lock (self ):
123
127
"""
124
128
Return
@@ -133,13 +137,13 @@ def _has_lock(self):
133
137
lock_file = self ._lock_file_path ()
134
138
try :
135
139
fp = open (lock_file , "rb" )
136
- pid = int ( fp .read () )
140
+ pid = fp .read ()
137
141
fp .close ()
138
142
except IOError :
139
143
raise AssertionError ("The lock file at %s could not be read" % lock_file )
140
144
141
- if pid != os . getpid ():
142
- raise AssertionError ("We claim to own the lock at %s, but it was not owned by our process %i , but by %i " % (lock_file , os . getpid (), pid ))
145
+ if pid != self . _get_id ():
146
+ raise AssertionError ("We claim to own the lock at %s, but it was not owned by our process %r , but by %r " % (lock_file , self . _get_id (), pid ))
143
147
144
148
return True
145
149
@@ -152,14 +156,25 @@ def _obtain_lock_or_raise(self):
152
156
"""
153
157
if self ._has_lock ():
154
158
return
155
-
156
159
lock_file = self ._lock_file_path ()
157
- if os .path .exists (lock_file ):
160
+ if os .path .isfile (lock_file ):
158
161
raise IOError ("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self ._file_path , lock_file ))
159
-
162
+
163
+ my_id = self ._get_id ()
160
164
fp = open (lock_file , "wb" )
161
- fp .write (str (os .getpid ()))
165
+ fp .write (my_id )
166
+ fp .close ()
167
+
168
+ # verify its truly us who got the lock - if two threads are doing this within the
169
+ # fraction of a millisecond, it is possible to actually trick the FS
170
+ # and two threads write, but only one succeeds.
171
+ fp = open (lock_file , 'rb' )
172
+ actual_id = fp .read ()
162
173
fp .close ()
174
+ if actual_id != my_id :
175
+ msg = "Failed to obtain lock for file %r as the process identified by %r outraced this process or thread %r" % (self ._file_path , actual_id , my_id )
176
+ raise IOError (msg )
177
+ # END verification
163
178
164
179
self ._owns_lock = True
165
180
@@ -175,11 +190,11 @@ def _release_lock(self):
175
190
Release our lock if we have one
176
191
"""
177
192
if not self ._has_lock ():
178
- return
179
-
193
+ return
180
194
os .remove (self ._lock_file_path ())
181
195
self ._owns_lock = False
182
196
197
+
183
198
class BlockingLockFile (LockFile ):
184
199
"""The lock file will block until a lock could be obtained, or fail after
185
200
a specified timeout"""
@@ -206,7 +221,7 @@ def _obtain_lock(self):
206
221
maxtime = starttime + float (self ._max_block_time )
207
222
while True :
208
223
try :
209
- self . _obtain_lock_or_raise ()
224
+ super ( BlockingLockFile , self ). _obtain_lock ()
210
225
except IOError :
211
226
curtime = time .time ()
212
227
if curtime >= maxtime :
@@ -219,7 +234,6 @@ def _obtain_lock(self):
219
234
# END endless loop
220
235
221
236
222
-
223
237
class ConcurrentWriteOperation (LockFile ):
224
238
"""
225
239
This class facilitates a safe write operation to a file on disk such that we:
0 commit comments