Skip to content

Instantly share code, notes, and snippets.

@JavaScriptDude
Last active December 1, 2025 09:08
Show Gist options
  • Select an option

  • Save JavaScriptDude/b436bfda31e765ef7a9916890c786138 to your computer and use it in GitHub Desktop.

Select an option

Save JavaScriptDude/b436bfda31e765ef7a9916890c786138 to your computer and use it in GitHub Desktop.
Cross Platform Example of Single Instance Checking in Python
import time, sys, os
class SingleInstanceChecker:
def __init__(self, id):
if isWin():
ensure_win32api()
self.mutexname = id
self.lock = win32event.CreateMutex(None, False, self.mutexname)
self.running = (win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS)
else:
ensure_fcntl()
self.lock = open(f"/tmp/isnstance_{id}.lock", 'wb')
try:
fcntl.lockf(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
self.running = False
except IOError:
self.running = True
def already_running(self):
return self.running
def __del__(self):
if self.lock:
try:
if isWin():
win32api.CloseHandle(self.lock)
else:
os.close(self.lock)
except Exception as ex:
pass
# ---------------------------------------
# Utility Functions
# Dynamically load win32api on demand
# Install with: pip install pywin32
win32api=winerror=win32event=None
def ensure_win32api():
global win32api,winerror,win32event
if win32api is None:
import win32api
import winerror
import win32event
# Dynamically load fcntl on demand (python stdlib)
fcntl=None
def ensure_fcntl():
global fcntl
if fcntl is None:
import fcntl
def isWin():
return (os.name == 'nt')
# ---------------------------------------
def main(argv):
_timeout = 10
print("main() called. sleeping for %s seconds" % _timeout)
time.sleep(_timeout)
print("DONE")
if __name__ == '__main__':
SCR_NAME = "my_script"
sic = SingleInstanceChecker(SCR_NAME)
if sic.already_running():
print("An instance of {} is already running.".format(SCR_NAME))
sys.exit(1)
else:
main(sys.argv[1:])
@Tuhin-thinks
Copy link

Tuhin-thinks commented Aug 15, 2023

Great job @JavaScriptDude !

@dineshbvadhia
Copy link

Where is the fcntl package from because get these errors?:

> pip install fcntl
ERROR: Could not find a version that satisfies the requirement fcntl (from versions: none)
ERROR: No matching distribution found for fcntl

@JavaScriptDude
Copy link
Author

@dineshbvadhia Was a typo. fcntl is built into the python stdlib. Thanks for letting me know.

@dineshbvadhia
Copy link

Hi. Typed the code but mypy generates a bunch of 'has no attribute' errors which I'm not sure how to resolve:

from typing import Any
import time, sys, os

class SingleInstanceChecker:
    def __init__(self, id: str) -> None:
        if isWin():
            ensure_win32api()
            self.mutexname = id
            self.lock = win32event.CreateMutex(None, False, self.mutexname)
            self.running = (win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS)

        else:
            ensure_fcntl()
            self.lock = open(f"/tmp/isnstance_{id}.lock", 'wb')
            try:
                fcntl.lockf(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
                self.running = False
            except IOError:
                self.running = True

    def already_running(self) -> Any:
        return self.running
        
    def __del__(self) -> None:
        if self.lock:
            try:
                if isWin():
                    win32api.CloseHandle(self.lock)
                else:
                    os.close(self.lock)
            except Exception as ex:
                pass

# ---------------------------------------
# Utility Functions
# Dynamically load win32api on demand
# Install with: pip install pywin32
win32api=winerror=win32event=None
def ensure_win32api() -> None:
    global win32api,winerror,win32event
    if win32api is None:
        import win32api             
        import winerror             
        import win32event           

# Dynamically load fcntl on demand (python stdlib)
fcntl=None
def ensure_fcntl() -> None:
    global fcntl
    if fcntl is None:
        import fcntl


def isWin() -> bool:
    return (os.name == 'nt')
# ---------------------------------------

def main(argv: list[str]) -> None:
    _timeout: int = 10
    print("main() called. sleeping for %s seconds" % _timeout)
    time.sleep(_timeout)
    print("DONE")


if __name__ == '__main__':
    SCR_NAME: str = "my_script"
    sic = SingleInstanceChecker(SCR_NAME)
    if sic.already_running():
        print("An instance of {} is already running.".format(SCR_NAME))
        sys.exit(1)
    else:
        main(sys.argv[1:])
> mypy SingleInstanceChecker.py
py:9: error: "None" has no attribute "CreateMutex"  [attr-defined]
py:10: error: "None" has no attribute "GetLastError"  [attr-defined]
py:10: error: "None" has no attribute "ERROR_ALREADY_EXISTS"  [attr-defined]
py:16: error: "None" has no attribute "lockf"  [attr-defined]
py:16: error: "None" has no attribute "LOCK_EX"  [attr-defined]
py:16: error: "None" has no attribute "LOCK_NB"  [attr-defined]
py:28: error: "None" has no attribute "CloseHandle"  [attr-defined]
Found 7 errors in 1 file (checked 1 source file)

@JavaScriptDude
Copy link
Author

Although I've never used mypy. I suspect that mypy is getting confused by the dynamic loading of the platform specific library via ensure_win32api and ensure_fcntl. I'm sure if you made a linux and windows specific versions and explicitly load the respective library, mypy will not have issues.

There may be a more pythonic way to do this type of platform specific dynamic loading that would make mypy happy but I don't have the bandwidth to investigate. Suggestions would be appreciated.

@JavaScriptDude
Copy link
Author

The code definitely works and I've used it in dozens of usecases where I develop the code in linux and deploy in windows in some cases. I use this code for my scheduled task / cron based jobs.

@dineshbvadhia
Copy link

@JavaScriptDude Apologies if I gave the impression the code doesn't work - it does work. My code base is typed and so wanted to type this. I'll investigate further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment