Welcome, guest | Sign In | My Account | Store | Cart

Yet another way to get a single instance application. This recipe uses file locking only.

Python, 68 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import sys
import os

try:
    import fcntl
except ImportError:
    fcntl = None

LOCK_PATH = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "lock")
OS_WIN = False
if 'win32' in sys.platform.lower():
    OS_WIN = True


class SingleInstance:
    def __init__(self):
        self.fh = None
        self.is_running = False
        self.do_magic()

    def do_magic(self):
        if OS_WIN:
            try:
                if os.path.exists(LOCK_PATH):
                    os.unlink(LOCK_PATH)
                self.fh = os.open(LOCK_PATH, os.O_CREAT | os.O_EXCL | os.O_RDWR)
            except EnvironmentError as err:
                if err.errno == 13:
                    self.is_running = True
                else:
                    raise
        else:
            try:
                self.fh = open(LOCK_PATH, 'w')
                fcntl.lockf(self.fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except EnvironmentError as err:
                if self.fh is not None:
                    self.is_running = True
                else:
                    raise

    def clean_up(self):
        # this is not really needed
        try:
            if self.fh is not None:
                if OS_WIN:
                    os.close(self.fh)
                    os.unlink(LOCK_PATH)
                else:
                    fcntl.lockf(self.fh, fcntl.LOCK_UN)
                    self.fh.close() # ???
                    os.unlink(LOCK_PATH)
        except Exception as err:
            # logger.exception(err)
            raise # for debugging porpuses, do not raise it on production


if __name__ == "__main__":
    import time

    si = SingleInstance()
    try:
        if si.is_running:
            sys.exit("This app is already running!")
        time.sleep(20) # remove
        # do other stuff
    finally:
        si.clean_up()

Well, as long as the file is created, it's a reliable solution to this common issue.

If the file can not be created, it's your desission to exit or ignore it, allowing multiple instances.

Even if the process dies, the file should get unlocked (tested on Windows).

For multi session/users, you must create the file on the user directory

LOCK_PATH = os.path.join(os.path.expanduser("~"), "lock")

3 comments

Nilay 8 years, 3 months ago  # | flag

Too much code for such small stuff. Could be easily achieved using Window Mutex:

import win32event
import win32api
from winerror import ERROR_ALREADY_EXISTS

mutex = win32event.CreateMutex(None, False, name)
last_error = win32api.GetLastError()

if last_error == ERROR_ALREADY_EXISTS:
   Print "App instance already running"
Esteban Castro Borsani (author) 8 years, 3 months ago  # | flag

This recipe is cross platform, meaning it will work on Windows, Linux, MacOS.

I would rather use sockets, though.

Nilay 8 years, 3 months ago  # | flag

Not sure about Linux but OS X have even simpler to prevent multiple instances by putting following entry in plist:

LSMultipleInstancesProhibited:True