-
-
Save JavaScriptDude/b436bfda31e765ef7a9916890c786138 to your computer and use it in GitHub Desktop.
| 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:]) |
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
@dineshbvadhia Was a typo. fcntl is built into the python stdlib. Thanks for letting me know.
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)
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.
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.
@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.
Great job @JavaScriptDude !