How can one open a file for exclusive access?

Joshua Muskovitz joshm at taconic.net
Tue Jan 15 00:06:22 CET 2002


Here's a chunk of code that deals with lockfiles (for preventing multiple
instances of apps, etc) and works on NT and Solaris (I haven't tested it on
other *nixen).  Also note this is 1.5.2 code -- some things may break in 2.x
(like the nested import statement).  The nice thing about this code is that
if your process dies unexpectedly, the lock is freed automatically by the
OS.

============= cut here ============

class InstanceException(exceptions.Exception):
    BAD_MAGIC    = 1 # if the lock file exists but does not contain the
right magic
    ALREADY_LOCKED = 2 # if the lock file exists
    UNSUPPORTED_OS = 3 # if the OS just can't cope

    def __init__(self, errno, msg):
        self.errno = errno
        self.msg = msg
        self.args = msg

class SingleInstance:
    def __init__(self, filename, magic = "SingleInstance Magic File --
Shh!"):
        # store the filename
        self.filename = filename
        self.fd = None # Note:  This has different meaning under NT and
Posix
        self.magic = magic
        self.CreateLock()

    def CreateLock(self):
        # if we are already locked, then just return
        if self.fd != None:
            return

        # if the file exists, then make sure it is a special
        # SingleInstance file by checking the first line for
        # the magic string
        try:
            fd = os.open(self.filename, os.O_EXCL|os.O_RDONLY)
            magic = os.read(fd, len(self.magic))
            os.close(fd)
            if magic != self.magic:
                raise InstanceException, (InstanceException.BAD_MAGIC, "File
exists and contains bad magic!")

        except exceptions.OSError, e:
            # the only legal error here is a "No such file or directory"
            if e.errno == 2: # No such file or directory
                pass
            else:
                raise e

        if os.name == 'nt':
            ###########################################################
            #
            # NT SPECIFIC LOCK ACQUISITION METHOD

            # Try to delete the lock file.
            # This will fail if another process has the lock.
            try:
                os.remove(self.filename)
            except exceptions.OSError, e:
                if e.errno == 2:   # No such file or directory
                    pass
                elif e.errno == 13: # Permission denied
                    raise InstanceException,
(InstanceException.ALREADY_LOCKED, "Another process owns this lock!")

            # Next, try to create the file with an exclusive lock.
            # This will fail if another process has the lock.
            try:
                self.fd = os.open(self.filename,
os.O_CREAT|os.O_EXCL|os.O_WRONLY)
                os.write(self.fd, self.magic)
            except exceptions.OSError, e:
                raise InstanceException, (InstanceException.ALREADY_LOCKED,
"Unable to create lock!")

            # END OF NT SPECIFIC LOCK ACQUISITION METHOD
            #
            ###########################################################
        elif os.name == 'posix':
            ###########################################################
            #
            # POSIX SPECIFIC LOCK ACQUISITION METHOD

            # Don't delete the lock file.

            # Try to create the file.  This *should* always succeed.
            try:
                self.fd = open(self.filename, 'w')
            except:
                raise InstanceException, (InstanceException.ALREADY_LOCKED,
"Unable to create lock!")

            # Attempt to lock the file.  This will fail if another
            # process already has the lock.
            try:
                import fcntl
                fcntl.flock(self.fd.fileno(),  fcntl.LOCK_EX|fcntl.LOCK_NB)
            except exceptions.IOError, e:
                # another process has the lock, but we've just trashed
                # the file.  write the magic and close it!
                self.fd.write(self.magic)
                self.fd.close()
                self.fd = None
                raise InstanceException, (InstanceException.ALREADY_LOCKED,
"Another process owns this lock!")

            # Write the magic to the file
            try:
                self.fd.write(self.magic)
                self.fd.flush()
            except:
                raise InstanceException, (InstanceException.ALREADY_LOCKED,
"Unable to create lock!")

            # END OF POSIX SPECIFIC LOCK ACQUISITION METHOD
            #
            ###########################################################
        else:
            ###########################################################
            #
            # Everyone else

            raise InstanceException, (InstanceException.UNSUPPORTED_OS,
"This OS isn't supported!")

            #
            ###########################################################

    def FreeLock(self):
        if self.fd != None:
            # if there is a lockfile open, then close it
            # note that self.fd holds different things based on the OS
            if os.name == 'nt':
                os.close(self.fd)
            elif os.name == 'posix':
                self.fd.close()
            else:
                raise InstanceException, (InstanceException.UNSUPPORTED_OS,
"This OS isn't supported!")

            self.fd = None
            os.remove(self.filename)

    def TouchLock(self):
        if self.fd != None:
            # if there is a lockfile open, touch it
            # this works on all OSes.
            now = time.time()
            os.utime(self.filename, (now, now))

    def __del__(self):
        self.FreeLock()

============= end of code ============

And here's how to use it...

    try:
        filename = *** PLACE YOUR FILENAME HERE ***
        ourLock = SingleInstance(filename)
    except InstanceException, e:
        if e.errno == InstanceException.ALREADY_LOCKED:
            *** HANDLE YOUR EXCEPTION HERE ***

   ...

    ourLock.TouchLock() # this updates the timestamp on the lockfile,
    # which may be useful to you (to allow other processes to see that
    # you are still alive, for example)

    ...

    ourLock.FreeLock() # frees the lock, and deletes the file

Enjoy.

-- josh




-----= Posted via Newsfeeds.Com, Uncensored Usenet News =-----
http://www.newsfeeds.com - The #1 Newsgroup Service in the World!
-----==  Over 80,000 Newsgroups - 16 Different Servers! =-----



More information about the Python-list mailing list