@@ -119,6 +119,10 @@ def _lock_file_path(self):
119119 """
120120 return "%s.lock" % (self ._file_path )
121121
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+
122126 def _has_lock (self ):
123127 """
124128 Return
@@ -133,13 +137,13 @@ def _has_lock(self):
133137 lock_file = self ._lock_file_path ()
134138 try :
135139 fp = open (lock_file , "rb" )
136- pid = int ( fp .read () )
140+ pid = fp .read ()
137141 fp .close ()
138142 except IOError :
139143 raise AssertionError ("The lock file at %s could not be read" % lock_file )
140144
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 ))
143147
144148 return True
145149
@@ -152,14 +156,25 @@ def _obtain_lock_or_raise(self):
152156 """
153157 if self ._has_lock ():
154158 return
155-
156159 lock_file = self ._lock_file_path ()
157- if os .path .exists (lock_file ):
160+ if os .path .isfile (lock_file ):
158161 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 ()
160164 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 ()
162173 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
163178
164179 self ._owns_lock = True
165180
@@ -175,11 +190,11 @@ def _release_lock(self):
175190 Release our lock if we have one
176191 """
177192 if not self ._has_lock ():
178- return
179-
193+ return
180194 os .remove (self ._lock_file_path ())
181195 self ._owns_lock = False
182196
197+
183198class BlockingLockFile (LockFile ):
184199 """The lock file will block until a lock could be obtained, or fail after
185200 a specified timeout"""
@@ -206,7 +221,7 @@ def _obtain_lock(self):
206221 maxtime = starttime + float (self ._max_block_time )
207222 while True :
208223 try :
209- self . _obtain_lock_or_raise ()
224+ super ( BlockingLockFile , self ). _obtain_lock ()
210225 except IOError :
211226 curtime = time .time ()
212227 if curtime >= maxtime :
@@ -219,7 +234,6 @@ def _obtain_lock(self):
219234 # END endless loop
220235
221236
222-
223237class ConcurrentWriteOperation (LockFile ):
224238 """
225239 This class facilitates a safe write operation to a file on disk such that we:
0 commit comments