This patch adds in support for automatically detecting and decompressing
[catalyst.git] / modules / catalyst_lock.py
1 #!/usr/bin/python
2 import os
3 import fcntl
4 import errno
5 import sys
6 import string
7 import time
8 from catalyst_support import *
9
10 def writemsg(mystr):
11         sys.stderr.write(mystr)
12         sys.stderr.flush()
13
14 class LockDir:
15         locking_method=fcntl.flock
16         lock_dirs_in_use=[]
17         die_on_failed_lock=True
18         def __del__(self):
19                 self.clean_my_hardlocks()
20                 self.delete_lock_from_path_list()
21                 if self.islocked():
22                         self.fcntl_unlock()
23         def __init__(self,lockdir):
24                 self.locked=False
25                 self.myfd=None
26                 self.set_gid(250)
27                 self.locking_method=LockDir.locking_method
28                 self.set_lockdir(lockdir)
29                 self.set_lockfilename(".catalyst_lock")
30                 self.set_lockfile()
31         
32                 if LockDir.lock_dirs_in_use.count(lockdir)>0:
33                         raise "This directory already associated with a lock object"
34                 else:
35                         LockDir.lock_dirs_in_use.append(lockdir)
36         
37                 self.hardlock_paths={}
38         
39
40
41         def delete_lock_from_path_list(self):
42                 i=0
43                 try:
44                         if LockDir.lock_dirs_in_use:
45                                 for x in LockDir.lock_dirs_in_use:
46                                         if LockDir.lock_dirs_in_use[i] == self.lockdir:
47                                                 del LockDir.lock_dirs_in_use[i]
48                                                 break
49                                                 i=i+1
50                 except AttributeError:
51                         pass
52
53         def islocked(self):
54                 if self.locked:
55                         return True
56                 else:
57                         return False
58
59         def set_gid(self,gid):
60                 if not self.islocked():
61 #                       if self.settings.has_key("DEBUG"):
62 #                               print "setting gid to", gid 
63                         self.gid=gid
64
65         def set_lockdir(self,lockdir):
66                 if not os.path.exists(lockdir):
67                         os.makedirs(lockdir)
68                 if os.path.isdir(lockdir):
69                         if not self.islocked():
70                                 if lockdir[-1] == "/":
71                                         lockdir=lockdir[:-1]
72                                 self.lockdir=normpath(lockdir)
73 #                               if self.settings.has_key("DEBUG"):
74 #                                       print "setting lockdir to", self.lockdir
75                 else:
76                         raise "the lock object needs a path to a dir"
77
78         def set_lockfilename(self,lockfilename):
79                 if not self.islocked():
80                         self.lockfilename=lockfilename
81 #                       if self.settings.has_key("DEBUG"):
82 #                               print "setting lockfilename to", self.lockfilename
83
84         def set_lockfile(self):
85                 if not self.islocked():
86                         self.lockfile=normpath(self.lockdir+'/'+self.lockfilename)
87 #                       if self.settings.has_key("DEBUG"):
88 #                               print "setting lockfile to", self.lockfile
89
90         def read_lock(self):
91                 if not self.locking_method == "HARDLOCK":
92                         self.fcntl_lock("read")
93                 else:
94                         print "HARDLOCKING doesnt support shared-read locks"
95                         print "using exclusive write locks"
96                         self.hard_lock()
97     
98         def write_lock(self):
99                 if not self.locking_method == "HARDLOCK":
100                         self.fcntl_lock("write")
101                 else:
102                         self.hard_lock()
103
104         def unlock(self):
105                 if not self.locking_method == "HARDLOCK":
106                         self.fcntl_unlock()
107                 else:
108                         self.hard_unlock()
109
110         def fcntl_lock(self,locktype):
111                 if self.myfd==None:
112                         if not os.path.exists(os.path.dirname(self.lockdir)):
113                                 raise DirectoryNotFound, os.path.dirname(self.lockdir)
114                         if not os.path.exists(self.lockfile):
115                                 old_mask=os.umask(000)
116                                 self.myfd = os.open(self.lockfile, os.O_CREAT|os.O_RDWR,0660)
117                                 try:
118                                         if os.stat(self.lockfile).st_gid != self.gid:
119                                                 os.chown(self.lockfile,os.getuid(),self.gid)
120                                 except SystemExit, e:
121                                         raise
122                                 except OSError, e:
123                                         if e[0] == 2: #XXX: No such file or directory
124                                                 return self.fcntl_locking(locktype)
125                                         else:
126                                                 writemsg("Cannot chown a lockfile. This could cause inconvenience later.\n")
127
128                                 os.umask(old_mask)
129                         else:
130                                 self.myfd = os.open(self.lockfile, os.O_CREAT|os.O_RDWR,0660)
131         
132                 try:
133                         if locktype == "read":
134                                 self.locking_method(self.myfd,fcntl.LOCK_SH|fcntl.LOCK_NB)
135                         else:
136                                 self.locking_method(self.myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
137                 except IOError, e:
138                         if "errno" not in dir(e):
139                                 raise
140                         if e.errno == errno.EAGAIN:
141                                 if not LockDir.die_on_failed_lock:
142                                         # Resource temp unavailable; eg, someone beat us to the lock.
143                                         writemsg("waiting for lock on %s\n" % self.lockfile)
144
145                                         # Try for the exclusive or shared lock again.
146                                         if locktype == "read":
147                                                 self.locking_method(self.myfd,fcntl.LOCK_SH)
148                                         else:
149                                                 self.locking_method(self.myfd,fcntl.LOCK_EX)
150                                 else:
151                                         raise LockInUse,self.lockfile
152                         elif e.errno == errno.ENOLCK:
153                                 pass
154                         else:
155                                 raise
156                 if not os.path.exists(self.lockfile):
157                         os.close(self.myfd)
158                         self.myfd=None
159                         #writemsg("lockfile recurse\n")
160                         self.fcntl_lock(locktype)
161                 else:
162                         self.locked=True
163                         #writemsg("Lockfile obtained\n")
164             
165                             
166         def fcntl_unlock(self):
167                 import fcntl
168                 unlinkfile = 1
169                 if not os.path.exists(self.lockfile):
170                         print "lockfile does not exist '%s'" % self.lockfile
171                         if (self.myfd != None):
172                                 try:
173                                         os.close(myfd)
174                                         self.myfd=None
175                                 except:
176                                         pass
177                                 return False
178
179                         try:
180                                 if self.myfd == None:
181                                         self.myfd = os.open(self.lockfile, os.O_WRONLY,0660)
182                                         unlinkfile = 1
183                                         self.locking_method(self.myfd,fcntl.LOCK_UN)
184                         except SystemExit, e:
185                                 raise
186                         except Exception, e:
187                                 os.close(self.myfd)
188                                 self.myfd=None
189                                 raise IOError, "Failed to unlock file '%s'\n" % self.lockfile
190                                 try:
191                                         # This sleep call was added to allow other processes that are
192                                         # waiting for a lock to be able to grab it before it is deleted.
193                                         # lockfile() already accounts for this situation, however, and
194                                         # the sleep here adds more time than is saved overall, so am
195                                         # commenting until it is proved necessary.
196                                         #time.sleep(0.0001)
197                                         if unlinkfile:
198                                                 InUse=False
199                                                 try:
200                                                         self.locking_method(self.myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
201                                                 except:
202                                                         print "Read lock may be in effect. skipping lockfile delete..."
203                                                         InUse=True
204                                                         # We won the lock, so there isn't competition for it.
205                                                         # We can safely delete the file.
206                                                         #writemsg("Got the lockfile...\n")
207                                                         #writemsg("Unlinking...\n")
208                                                         self.locking_method(self.myfd,fcntl.LOCK_UN)
209                                         if not InUse:
210                                                 os.unlink(self.lockfile)
211                                                 os.close(self.myfd)
212                                                 self.myfd=None
213 #                                               if self.settings.has_key("DEBUG"):
214 #                                                       print "Unlinked lockfile..."
215                                 except SystemExit, e:
216                                         raise
217                                 except Exception, e:
218                                         # We really don't care... Someone else has the lock.
219                                         # So it is their problem now.
220                                         print "Failed to get lock... someone took it."
221                                         print str(e)
222
223                                         # Why test lockfilename?  Because we may have been handed an
224                                         # fd originally, and the caller might not like having their
225                                         # open fd closed automatically on them.
226                                         #if type(lockfilename) == types.StringType:
227                                         #        os.close(myfd)
228         
229                 if (self.myfd != None):
230                         os.close(self.myfd)
231                         self.myfd=None
232                         self.locked=False
233                         time.sleep(.0001)
234
235         def hard_lock(self,max_wait=14400):
236                 """Does the NFS, hardlink shuffle to ensure locking on the disk.
237                 We create a PRIVATE lockfile, that is just a placeholder on the disk.
238                 Then we HARDLINK the real lockfile to that private file.
239                 If our file can 2 references, then we have the lock. :)
240                 Otherwise we lather, rise, and repeat.
241                 We default to a 4 hour timeout.
242                 """
243
244                 self.myhardlock = self.hardlock_name(self.lockdir)
245
246                 start_time = time.time()
247                 reported_waiting = False
248
249                 while(time.time() < (start_time + max_wait)):
250                         # We only need it to exist.
251                         self.myfd = os.open(self.myhardlock, os.O_CREAT|os.O_RDWR,0660)
252                         os.close(self.myfd)
253
254                         self.add_hardlock_file_to_cleanup()
255                         if not os.path.exists(self.myhardlock):
256                                 raise FileNotFound, "Created lockfile is missing: %(filename)s" % {"filename":self.myhardlock}
257                         try:
258                                 res = os.link(self.myhardlock, self.lockfile)
259                         except SystemExit, e:
260                                 raise
261                         except Exception, e:
262 #                               if self.settings.has_key("DEBUG"):
263 #                                       print "lockfile(): Hardlink: Link failed."
264 #                                       print "Exception: ",e
265                                 pass
266
267                         if self.hardlink_is_mine(self.myhardlock, self.lockfile):
268                                 # We have the lock.
269                                 if reported_waiting:
270                                         print
271                                 return True
272
273                         if reported_waiting:
274                                 writemsg(".")
275                         else:
276                                 reported_waiting = True
277                                 print
278                                 print "Waiting on (hardlink) lockfile: (one '.' per 3 seconds)"
279                                 print "Lockfile: " + self.lockfile
280                         time.sleep(3)
281
282                 os.unlink(self.myhardlock)
283                 return False
284
285         def hard_unlock(self):
286                 try:
287                         if os.path.exists(self.myhardlock):
288                                 os.unlink(self.myhardlock)
289                         if os.path.exists(self.lockfile):
290                                 os.unlink(self.lockfile)
291                 except SystemExit, e:
292                         raise
293                 except:
294                         writemsg("Something strange happened to our hardlink locks.\n")
295
296         def add_hardlock_file_to_cleanup(self):
297                 #mypath = self.normpath(path)
298                 if os.path.isdir(self.lockdir) and os.path.isfile(self.myhardlock):
299                         self.hardlock_paths[self.lockdir]=self.myhardlock
300     
301         def remove_hardlock_file_from_cleanup(self):
302                 if self.hardlock_paths.has_key(self.lockdir):
303                         del self.hardlock_paths[self.lockdir]
304                         print self.hardlock_paths
305
306         def hardlock_name(self, path):
307                 mypath=path+"/.hardlock-"+os.uname()[1]+"-"+str(os.getpid())
308                 newpath = os.path.normpath(mypath)
309                 if len(newpath) > 1:
310                         if newpath[1] == "/":
311                                 newpath = "/"+newpath.lstrip("/")
312                 return newpath
313
314
315         def hardlink_is_mine(self,link,lock):
316                 import stat
317                 try:
318                         myhls = os.stat(link)
319                         mylfs = os.stat(lock)
320                 except SystemExit, e:
321                         raise
322                 except:
323                         myhls = None
324                         mylfs = None
325
326                 if myhls:
327                         if myhls[stat.ST_NLINK] == 2:
328                                 return True
329                 if mylfs:
330                         if mylfs[stat.ST_INO] == myhls[stat.ST_INO]:
331                                 return True
332                 return False
333
334         def hardlink_active(lock):
335                 if not os.path.exists(lock):
336                         return False
337
338         def clean_my_hardlocks(self):
339                 try:
340                         for x in self.hardlock_paths.keys():
341                                 self.hardlock_cleanup(x)
342                 except AttributeError:
343                         pass
344
345         def hardlock_cleanup(self,path):
346                 mypid  = str(os.getpid())
347                 myhost = os.uname()[1]
348                 mydl = os.listdir(path)
349                 results = []
350                 mycount = 0
351
352                 mylist = {}
353                 for x in mydl:
354                         filepath=path+"/"+x
355                         if os.path.isfile(filepath):
356                                 parts = filepath.split(".hardlock-")
357                         if len(parts) == 2:
358                                 filename = parts[0]
359                                 hostpid  = parts[1].split("-")
360                                 host  = "-".join(hostpid[:-1])
361                                 pid   = hostpid[-1]
362                         if not mylist.has_key(filename):
363                                 mylist[filename] = {}
364                             
365                         if not mylist[filename].has_key(host):
366                                 mylist[filename][host] = []
367                                 mylist[filename][host].append(pid)
368                                 mycount += 1
369                         else:
370                                 mylist[filename][host].append(pid)
371                                 mycount += 1
372
373
374                 results.append("Found %(count)s locks" % {"count":mycount})
375                 for x in mylist.keys():
376                         if mylist[x].has_key(myhost):
377                                 mylockname = self.hardlock_name(x)
378                                 if self.hardlink_is_mine(mylockname, self.lockfile) or \
379                                         not os.path.exists(self.lockfile):
380                                         for y in mylist[x].keys():
381                                                 for z in mylist[x][y]:
382                                                         filename = x+".hardlock-"+y+"-"+z
383                                                         if filename == mylockname:
384                                                                 self.hard_unlock()
385                                                                 continue
386                                                         try:
387                                                                 # We're sweeping through, unlinking everyone's locks.
388                                                                 os.unlink(filename)
389                                                                 results.append("Unlinked: " + filename)
390                                                         except SystemExit, e:
391                                                                 raise
392                                                         except Exception,e:
393                                                                 pass
394                                         try:
395                                                 os.unlink(x)
396                                                 results.append("Unlinked: " + x)
397                                                 os.unlink(mylockname)
398                                                 results.append("Unlinked: " + mylockname)
399                                         except SystemExit, e:
400                                                 raise
401                                         except Exception,e:
402                                                 pass
403                                 else:
404                                         try:
405                                                 os.unlink(mylockname)
406                                                 results.append("Unlinked: " + mylockname)
407                                         except SystemExit, e:
408                                                 raise
409                                         except Exception,e:
410                                                 pass
411                 return results
412
413
414 if __name__ == "__main__":
415
416         def lock_work():
417                 print 
418                 for i in range(1,6):
419                         print i,time.time()
420                         time.sleep(1)
421                 print
422         def normpath(mypath):
423                 newpath = os.path.normpath(mypath)
424                 if len(newpath) > 1:
425                         if newpath[1] == "/":
426                                 newpath = "/"+newpath.lstrip("/")
427                 return newpath
428
429         print "Lock 5 starting"
430         import time
431         Lock1=LockDir("/tmp/lock_path")
432         Lock1.write_lock() 
433         print "Lock1 write lock"
434         
435         lock_work()
436         
437         Lock1.unlock() 
438         print "Lock1 unlock"
439         
440         Lock1.read_lock()
441         print "Lock1 read lock"
442         
443         lock_work()
444         
445         Lock1.unlock() 
446         print "Lock1 unlock"
447
448         Lock1.read_lock()
449         print "Lock1 read lock"
450         
451         Lock1.write_lock()
452         print "Lock1 write lock"
453         
454         lock_work()
455         
456         Lock1.unlock()
457         print "Lock1 unlock"
458         
459         Lock1.read_lock()
460         print "Lock1 read lock"
461         
462         lock_work()
463         
464         Lock1.unlock()
465         print "Lock1 unlock"
466 #Lock1.write_lock()
467 #time.sleep(2)
468 #Lock1.unlock()
469     ##Lock1.write_lock()
470     #time.sleep(2)
471     #Lock1.unlock()