views:

3158

answers:

8

So, is there a Pythonic way to have only one instance of program running?

The only reasonable solution I've come up with is trying to run it as a server on some port, then second program trying to bind to same port - fails. But it's not really a great idea, maybe there's something more lightweight than this?

(Take into consideration that program is expected to fail sometimes, i.e. segfault - so things like "lock file" won't work)

Update: the solutions offered are much more complex and less reliant than just having a port occupied with a non-existant server, so I'd have to go with that one.

+6  A: 

I don't know if it's pythonic enough, but in the Java world listening on a defined port is a pretty widely used solution, as it works on all major platforms and doesn't have any problems with crashing programs.

Another advantage of listening to a port is that you could send a command to the running instance. For example when the users starts the program a second time, you could send the running instance a command to tell it to open another window (that's what Firefox does, for example. I don't know if they use TCP ports or named pipes or something like that, 'though).

Joachim Sauer
I'm sorry, but which way is "that"?
Slava N
Sorry, I was refering to listing to a port, I've clarified my answer.
Joachim Sauer
+3  A: 

This may work.

  1. Attempt create a PID file to a known location. If you fail, someone has the file locked, you're done.

  2. When you finish normally, close and remove the PID file, so someone else can overwrite it.

You can wrap your program in a shell script that removes the PID file even if your program crashes.

You can, also, use the PID file to kill the program if it hangs.

S.Lott
+2  A: 

Using a lock-file is a quite common approach on unix. If it crashes, you have to clean up manually. You could stor the PID in the file, and on startup check if there is a process with this PID, overriding the lock-file if not. (However, you also need a lock around the read-file-check-pid-rewrite-file). You will find what you need for getting and checking pid in the os-package. The common way of checking if there exists a process with a given pid, is to send it a non-fatal signal.

Other alternatives could be combining this with flock or posix semaphores.

Opening a network socket, as saua proposed, would probably be the easiest and most portable.

Rolf Rander
+5  A: 

Use a pid file. You have some known location, "/path/to/pidfile" and at startup you do something like this (partially pseudocode because I'm pre-coffee and don't want to work all that hard):

import os, os.path
pidfilePath = """/path/to/pidfile"""
if os.path.exists(pidfilePath):
   pidfile = open(pidfilePath,"r")
   pidString = pidfile.read()
   if <pidString is equal to os.getpid()>:
      # something is real weird
      Sys.exit(BADCODE)
   else:
      <use ps or pidof to see if the process with pid pidString is still running>
      if  <process with pid == 'pidString' is still running>:
          Sys.exit(ALREADAYRUNNING)
      else:
          # the previous server must have crashed
          <log server had crashed>
          <reopen pidfilePath for writing>
          pidfile.write(os.getpid())
else:
    <open pidfilePath for writing>
    pidfile.write(os.getpid())

So, in other words, you're checking if a pidfile exists; if not, write your pid to that file. If the pidfile does exist, then check to see if the pid is the pid of a running process; if so, then you've got another live process running, so just shut down. If not, then the previous process crashed, so log it, and then write your own pid to the file in place of the old one. Then continue.

Charlie Martin
This has a race condition. The test-then-write sequence may raise an exception of two programs start almost simultaneously, find no file and try to open for write concurrently. It *should* raise an exception on one, allowing the other to proceed.
S.Lott
Well, I said I'd had no coffee yet.
Charlie Martin
+4  A: 

Simple, cross-platform solution, found in another question by zgoda:

import fcntl, sys
pid_file = 'program.pid'
fp = open(pid_file, 'w')
try:
    fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    # another instance is running
    sys.exit(0)

Alot like S.Lott's suggestion, but with the code.

Slava N
Out of curiosity: is this really cross-platform? Does it work on Windows?
Joachim Sauer
There is no `fcntl` module on Windows (though the functionality could be emulated).
J.F. Sebastian
This is not "cross platform" (and I did not pretend it is). For Windows you have to use mutexes to achieve similar result - but I don't do Windows anymore and I have no code to share.
zgoda
The problem here is that if you want to write any data to the pid_file (like the PID), you will lose it. See: http://coding.derkeiler.com/Archive/Python/comp.lang.python/2008-06/msg02683.html
benno
+3  A: 

You already found reply to similar question in another thread, so for completeness sake see how to achieve the same on Windows uning named mutex.

http://code.activestate.com/recipes/474070/

zgoda
+10  A: 

The above code should do the job, it is crossplatform and runs on Python 2.4-2.6. I tested it on Windows and Linux.

If you have any hints on how to improve it please say, I will update it.

import sys, os, errno, tempfile

class SingleInstance:
    def __init__(self):
        import sys
        self.lockfile = os.path.normpath(tempfile.gettempdir() + '/' + os.path.basename(__file__) + '.lock')
        if sys.platform == 'win32':
            try:
                # file already exists, we try to remove (in case previous execution was interrupted)
                if(os.path.exists(self.lockfile)):
                    os.unlink(self.lockfile)
                self.fd =  os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)
            except OSError, e:
                if e.errno == 13:
                    print "Another instance is already running, quitting."
                    sys.exit(-1)
                print e.errno
                raise
        else: # non Windows
            import fcntl, sys
            self.fp = open(self.lockfile, 'w')
            try:
                fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                print "Another instance is already running, quitting."
                sys.exit(-1)

    def __del__(self):
        import sys
        if sys.platform == 'win32':
            if hasattr(self, 'fd'):
                os.close(self.fd)
                os.unlink(self.lockfile)

me = SingleInstance()
Sorin Sbarnea
Seems to work just fine under Mac OS X, too.
Clinton Blackmore
+1  A: 

I've been wondering how to achieve just this, thanks to this question I've now. Could get any simpler.

try:
    import socket
    s = socket.socket()
    host = socket.gethostname()
    port = 35636    #make sure this port is not used on this system
    s.bind((host, port))
    my_singlerun_function()
except:
    pass
Roberto Rosario