0

Let's suppose we have a long script: Foo.pyx
If I execute it two times, they are going to run simultaneously.

How can I terminate the first one if I execute it two times?

Edit: terminating the later ones can also work, I just want only one instance to run at any time.

Chris
  • 951
  • 10
  • 26

2 Answers2

3

The traditional POSIX answer to this is to use a pidfile: a file stored in a consistent location that just holds the PID of the process.

The reason you want to store the PID is so that if the script is killed, a new copy of your program will be able to see that and take over, instead of falsely reporting that another copy is already running. (You can send a 0 signal with os.kill; if it succeeds, the process exists.)

There are libraries to do this for you and get all the details right, but a simple version is:

import os
import sys

PIDFILE = '/var/run/myscript.pid'

def is_running():
    try:
        with open(PIDFILE) as f:
            pid = int(next(f))
        return os.kill(pid, 0)
    except Exception:
        return False

if __name__ == '__main__':
    if is_running():
        print('Another copy is already running')
        sys.exit(0)
    with open(PIDFILE, 'w') as f:
        f.write(f'{os.getpid()}\n')

You probably want cleaner error handling in real code, and you need to think about races, but I've kept it simple to make the idea clear. (If you want to do this for real, you're probably better off using a library, or thinking through all the issues yourself, than copying code off SO.)


The traditional Windows answer is a little different. On Windows, it's very easy to get a mandatory exclusive lock on a file or other resource that's automatically released on exit. In fact, that's what happens by default when you open a file—but, unfortunately, it’s not what happens with Python’s open, which goes out of its way to allow sharing. You can use a library like win32api from PyWin32, or just ctypes, to call CreateFile requesting exclusive access. If you fail because the file is locked, there's another process running.

It’s also important to decide where to create the file. Normally you use a per-user temp directory rather than a global one, which means each user can have one copy of the program running, rather than one copy for the entire system.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • thx a lot, going to keep these in mind – Chris Apr 03 '18 at 02:27
  • 1
    Windows Python opens files with read-write sharing, so `open` is of no help here. You'd have to call `CreateFile` via PyWin32 or ctypes. Given the user's temp directory is used, this allows one instance per user. Alternatively, it may be necessary to allow only one instance per Windows session. For this, Python's mmap module supports per-session names, e.g. `m = mmap.mmap(-1, 4, tagname='python_foo').` If it's zero, we attempt to write our pid to the shared memory. Otherwise exit. After writing, read the pid back, if it's not ours, also exit. – Eryk Sun Apr 03 '18 at 02:30
  • @eryksun Oops, you’re right. My old code for this uses win32api to call CreateFile. I’ll edit the answer. – abarnert Apr 03 '18 at 02:31
1

When the script starts, check if a specific file exists. A good name choice is myscript.lock, but any name will do.

If it exists, the script should exit immediately. Otherwise create an empty file of that name and proceed with the script. Then remove the file when the script finishes.

John Gordon
  • 29,573
  • 7
  • 33
  • 58
  • this is actually a really clever way – Chris Apr 03 '18 at 02:26
  • 2
    Race conditions can be addressed with exclusive mode ("x"), but unless it's on Windows, or newer versions of Linux I think, you don't have a way to reliably delete the file if the script crashes or gets abruptly terminated. If it is on Windows, you can use `fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_TEMPORARY)`. This opens the file in delete-on-close mode, which is guaranteed to be deleted even if the process gets terminated. – Eryk Sun Apr 03 '18 at 02:49