Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lockfile.py 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # This module is taken from https://gist.github.com/ionrock/3015700
  2. # A file lock implementation that tries to avoid platform specific
  3. # issues. It is inspired by a whole bunch of different implementations
  4. # listed below.
  5. # - https://bitbucket.org/jaraco/yg.lockfile/src/6c448dcbf6e5/yg/lockfile/__init__.py
  6. # - http://svn.zope.org/zc.lockfile/trunk/src/zc/lockfile/__init__.py?rev=121133&view=markup
  7. # - http://stackoverflow.com/questions/489861/locking-a-file-in-python
  8. # - http://www.evanfosmark.com/2009/01/cross-platform-file-locking-support-in-python/
  9. # - http://packages.python.org/lockfile/lockfile.html
  10. # There are some tests below and a blog posting conceptually the
  11. # problems I wanted to try and solve. The tests reflect these ideas.
  12. # - http://ionrock.wordpress.com/2012/06/28/file-locking-in-python/
  13. # I'm not advocating using this package. But if you do happen to try it
  14. # out and have suggestions please let me know.
  15. import os
  16. import time
  17. class FileLocked(Exception):
  18. pass
  19. class FileLock(object):
  20. def __init__(self, lock_filename, timeout=None, force=False):
  21. self.lock_filename = '%s.lock' % lock_filename
  22. self.timeout = timeout
  23. self.force = force
  24. self._pid = str(os.getpid())
  25. # Store pid in a file in the same directory as desired lockname
  26. self.pid_filename = os.path.join(
  27. os.path.dirname(self.lock_filename),
  28. self._pid,
  29. ) + '.lock'
  30. def get_lock_pid(self):
  31. try:
  32. return int(open(self.lock_filename).read())
  33. except IOError:
  34. # If we can't read symbolic link, there are two possibilities:
  35. # 1. The symbolic link is dead (point to non existing file)
  36. # 2. Symbolic link is not there
  37. # In either case, we can safely release the lock
  38. self.release()
  39. def valid_lock(self):
  40. """
  41. See if the lock exists and is left over from an old process.
  42. """
  43. lock_pid = self.get_lock_pid()
  44. # If we're unable to get lock_pid
  45. if lock_pid is None:
  46. return False
  47. # this is our process
  48. if self._pid == lock_pid:
  49. return True
  50. # it is/was another process
  51. # see if it is running
  52. try:
  53. os.kill(lock_pid, 0)
  54. except OSError:
  55. self.release()
  56. return False
  57. # it is running
  58. return True
  59. def is_locked(self, force=False):
  60. # We aren't locked
  61. if not self.valid_lock():
  62. return False
  63. # We are locked, but we want to force it without waiting
  64. if not self.timeout:
  65. if self.force:
  66. self.release()
  67. return False
  68. else:
  69. # We're not waiting or forcing the lock
  70. raise FileLocked()
  71. # Locked, but want to wait for an unlock
  72. interval = .1
  73. intervals = int(self.timeout / interval)
  74. while intervals:
  75. if self.valid_lock():
  76. intervals -= 1
  77. time.sleep(interval)
  78. #print('stopping %s' % intervals)
  79. else:
  80. return True
  81. # check one last time
  82. if self.valid_lock():
  83. if self.force:
  84. self.release()
  85. else:
  86. # still locked :(
  87. raise FileLocked()
  88. def acquire(self):
  89. """Create a pid filename and create a symlink (the actual lock file)
  90. across platforms that points to it. Symlink is used because it's an
  91. atomic operation across platforms.
  92. """
  93. pid_file = os.open(self.pid_filename, os.O_CREAT | os.O_EXCL | os.O_RDWR)
  94. os.write(pid_file, str(os.getpid()).encode('utf-8'))
  95. os.close(pid_file)
  96. if hasattr(os, 'symlink'):
  97. os.symlink(self.pid_filename, self.lock_filename)
  98. else:
  99. # Windows platforms doesn't support symlinks, at least not through the os API
  100. self.lock_filename = self.pid_filename
  101. def release(self):
  102. """Try to delete the lock files. Doesn't matter if we fail"""
  103. if self.lock_filename != self.pid_filename:
  104. try:
  105. os.unlink(self.lock_filename)
  106. except OSError:
  107. pass
  108. try:
  109. os.remove(self.pid_filename)
  110. except OSError:
  111. pass
  112. def __enter__(self):
  113. if not self.is_locked():
  114. self.acquire()
  115. return self
  116. def __exit__(self, type, value, traceback):
  117. self.release()