comparison MoinMoin/wikiutil.py @ 1295:9608758dca9a

Fixed severe race conditions in the sync tags and the meta dict code. Before, multiple processes could destroy each other data by keeping two meta dicts instantiated and writing to them.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Mon, 14 Aug 2006 22:52:14 +0200
parents a7f8dceb4410
children 76a76def8687
comparison
equal deleted inserted replaced
1275:e083ea8c934e 1295:9608758dca9a
406 'data_format_revision', # for data_dir format spec (use by mig scripts) 406 'data_format_revision', # for data_dir format spec (use by mig scripts)
407 ] 407 ]
408 408
409 class MetaDict(dict): 409 class MetaDict(dict):
410 """ store meta informations as a dict. 410 """ store meta informations as a dict.
411 XXX It is not thread-safe, add locks!
412 """ 411 """
413 def __init__(self, metafilename, cache_directory): 412 def __init__(self, metafilename, cache_directory):
414 """ create a MetaDict from metafilename """ 413 """ create a MetaDict from metafilename """
415 dict.__init__(self) 414 dict.__init__(self)
416 self.metafilename = metafilename 415 self.metafilename = metafilename
417 self.dirty = False 416 self.dirty = False
418 self.loaded = False
419 lock_dir = os.path.join(cache_directory, '__metalock__') 417 lock_dir = os.path.join(cache_directory, '__metalock__')
420 self.rlock = lock.ReadLock(lock_dir, 60.0) 418 self.rlock = lock.ReadLock(lock_dir, 60.0)
421 self.wlock = lock.WriteLock(lock_dir, 60.0) 419 self.wlock = lock.WriteLock(lock_dir, 60.0)
420
421 if not self.rlock.acquire(3.0):
422 raise EnvironmentError("Could not lock in MetaDict")
423 try:
424 self._get_meta()
425 finally:
426 self.rlock.release()
422 427
423 def _get_meta(self): 428 def _get_meta(self):
424 """ get the meta dict from an arbitrary filename. 429 """ get the meta dict from an arbitrary filename.
425 does not keep state, does uncached, direct disk access. 430 does not keep state, does uncached, direct disk access.
426 @param metafilename: the name of the file to read 431 @param metafilename: the name of the file to read
427 @return: dict with all values or {} if empty or error 432 @return: dict with all values or {} if empty or error
428 """ 433 """
429 434
430 try: 435 try:
431 if not self.rlock.acquire(3.0): 436 metafile = codecs.open(self.metafilename, "r", "utf-8")
432 raise EnvironmentError("Could not lock in MetaDict") 437 meta = metafile.read() # this is much faster than the file's line-by-line iterator
433 try: 438 metafile.close()
434 metafile = codecs.open(self.metafilename, "r", "utf-8")
435 meta = metafile.read() # this is much faster than the file's line-by-line iterator
436 metafile.close()
437 finally:
438 self.rlock.release()
439 except IOError: 439 except IOError:
440 meta = u'' 440 meta = u''
441 for line in meta.splitlines(): 441 for line in meta.splitlines():
442 key, value = line.split(':', 1) 442 key, value = line.split(':', 1)
443 value = value.strip() 443 value = value.strip()
444 if key in INTEGER_METAS: 444 if key in INTEGER_METAS:
445 value = int(value) 445 value = int(value)
446 dict.__setitem__(self, key, value) 446 dict.__setitem__(self, key, value)
447 self.loaded = True
448 447
449 def _put_meta(self): 448 def _put_meta(self):
450 """ put the meta dict into an arbitrary filename. 449 """ put the meta dict into an arbitrary filename.
451 does not keep or modify state, does uncached, direct disk access. 450 does not keep or modify state, does uncached, direct disk access.
452 @param metafilename: the name of the file to write 451 @param metafilename: the name of the file to write
457 if key in INTEGER_METAS: 456 if key in INTEGER_METAS:
458 value = str(value) 457 value = str(value)
459 meta.append("%s: %s" % (key, value)) 458 meta.append("%s: %s" % (key, value))
460 meta = '\r\n'.join(meta) 459 meta = '\r\n'.join(meta)
461 460
461 metafile = codecs.open(self.metafilename, "w", "utf-8")
462 metafile.write(meta)
463 metafile.close()
464 filesys.chmod(self.metafilename, 0666 & config.umask)
465 self.dirty = False
466
467 def sync(self, mtime_usecs=None):
468 """ No-Op except for that parameter """
469 if not mtime_usecs is None:
470 self.__setitem__('mtime', str(mtime_usecs))
471 # otherwise no-op
472
473 def __getitem__(self, key):
474 """ We don't care for cache coherency here. """
475 return dict.__getitem__(self, key)
476
477 def __setitem__(self, key, value):
478 """ Sets a dictionary entry. """
462 if not self.wlock.acquire(5.0): 479 if not self.wlock.acquire(5.0):
463 raise EnvironmentError("Could not lock in MetaDict") 480 raise EnvironmentError("Could not lock in MetaDict")
464 try: 481 try:
465 metafile = codecs.open(self.metafilename, "w", "utf-8") 482 self._get_meta() # refresh cache
466 metafile.write(meta) 483 try:
467 metafile.close() 484 oldvalue = dict.__getitem__(self, key)
485 except KeyError:
486 oldvalue = None
487 if value != oldvalue:
488 dict.__setitem__(self, key, value)
489 self._put_meta() # sync cache
468 finally: 490 finally:
469 self.wlock.release() 491 self.wlock.release()
470 filesys.chmod(self.metafilename, 0666 & config.umask)
471 self.dirty = False
472
473 def sync(self, mtime_usecs=None):
474 """ sync the in-memory dict to the persistent store (if dirty) """
475 if self.dirty:
476 if not mtime_usecs is None:
477 self.__setitem__('mtime', str(mtime_usecs))
478 self._put_meta()
479
480 def __getitem__(self, key):
481 try:
482 return dict.__getitem__(self, key)
483 except KeyError:
484 if not self.loaded:
485 self._get_meta() # lazy loading of metadata
486 return dict.__getitem__(self, key)
487 else:
488 raise
489
490 def __setitem__(self, key, value):
491 """ Sets a dictionary entry. You actually have to call sync to write it
492 to the persistent store. """
493 try:
494 oldvalue = dict.__getitem__(self, key)
495 except KeyError:
496 oldvalue = None
497 if value != oldvalue:
498 dict.__setitem__(self, key, value)
499 self.dirty = True
500 492
501 493
502 ############################################################################# 494 #############################################################################
503 ### InterWiki 495 ### InterWiki
504 ############################################################################# 496 #############################################################################