1############################################################################## 2# 3# Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4# All Rights Reserved. 5# 6# This software is subject to the provisions of the Zope Public License, 7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11# FOR A PARTICULAR PURPOSE 12# 13############################################################################## 14 15import os 16import errno 17import logging 18logger = logging.getLogger("zc.lockfile") 19 20__metaclass__ = type 21 22class LockError(Exception): 23 """Couldn't get a lock 24 """ 25 26try: 27 import fcntl 28except ImportError: 29 try: 30 import msvcrt 31 except ImportError: 32 def _lock_file(file): 33 raise TypeError('No file-locking support on this platform') 34 def _unlock_file(file): 35 raise TypeError('No file-locking support on this platform') 36 37 else: 38 # Windows 39 def _lock_file(file): 40 # Lock just the first byte 41 try: 42 msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, 1) 43 except IOError: 44 raise LockError("Couldn't lock %r" % file.name) 45 46 def _unlock_file(file): 47 try: 48 file.seek(0) 49 msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1) 50 except IOError: 51 raise LockError("Couldn't unlock %r" % file.name) 52 53else: 54 # Unix 55 _flags = fcntl.LOCK_EX | fcntl.LOCK_NB 56 57 def _lock_file(file): 58 try: 59 fcntl.flock(file.fileno(), _flags) 60 except IOError: 61 raise LockError("Couldn't lock %r" % file.name) 62 63 def _unlock_file(file): 64 fcntl.flock(file.fileno(), fcntl.LOCK_UN) 65 66class LazyHostName: 67 """Avoid importing socket and calling gethostname() unnecessarily""" 68 def __str__(self): 69 import socket 70 return socket.gethostname() 71 72 73class SimpleLockFile: 74 75 _fp = None 76 77 def __init__(self, path): 78 self._path = path 79 try: 80 # Try to open for writing without truncation: 81 fp = open(path, 'r+') 82 except IOError: 83 # If the file doesn't exist, we'll get an IO error, try a+ 84 # Note that there may be a race here. Multiple processes 85 # could fail on the r+ open and open the file a+, but only 86 # one will get the the lock and write a pid. 87 fp = open(path, 'a+') 88 89 try: 90 _lock_file(fp) 91 self._fp = fp 92 except: 93 fp.close() 94 raise 95 96 # Lock acquired 97 self._on_lock() 98 fp.flush() 99 100 def close(self): 101 if self._fp is not None: 102 _unlock_file(self._fp) 103 self._fp.close() 104 self._fp = None 105 106 def _on_lock(self): 107 """ 108 Allow subclasses to supply behavior to occur following 109 lock acquisition. 110 """ 111 112 113class LockFile(SimpleLockFile): 114 115 def __init__(self, path, content_template='{pid}'): 116 self._content_template = content_template 117 super(LockFile, self).__init__(path) 118 119 def _on_lock(self): 120 content = self._content_template.format( 121 pid=os.getpid(), 122 hostname=LazyHostName(), 123 ) 124 self._fp.write(" %s\n" % content) 125 self._fp.truncate() 126