pidlock.py
145 lines
| 4.6 KiB
| text/x-python
|
PythonLexer
r1 | ||||
r5088 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
import os | ||||
import errno | ||||
from multiprocessing.util import Finalize | ||||
class LockHeld(Exception): | ||||
pass | ||||
class DaemonLock(object): | ||||
"""daemon locking | ||||
USAGE: | ||||
try: | ||||
l = DaemonLock(file_='/path/tolockfile',desc='test lock') | ||||
main() | ||||
l.release() | ||||
except LockHeld: | ||||
sys.exit(1) | ||||
""" | ||||
def __init__(self, file_=None, callbackfn=None, | ||||
desc='daemon lock', debug=False): | ||||
lock_name = os.path.join(os.path.dirname(__file__), 'running.lock') | ||||
self.pidfile = file_ if file_ else lock_name | ||||
self.callbackfn = callbackfn | ||||
self.desc = desc | ||||
self.debug = debug | ||||
self.held = False | ||||
r5021 | # run the lock automatically ! | |||
r1 | self.lock() | |||
self._finalize = Finalize(self, DaemonLock._on_finalize, | ||||
args=(self, debug), exitpriority=10) | ||||
@staticmethod | ||||
def _on_finalize(lock, debug): | ||||
if lock.held: | ||||
if debug: | ||||
r5021 | print('lock held finalazing and running lock.release()') | |||
r1 | lock.release() | |||
def lock(self): | ||||
""" | ||||
locking function, if lock is present it | ||||
will raise LockHeld exception | ||||
""" | ||||
r5021 | lockname = f'{os.getpid()}' | |||
r1 | if self.debug: | |||
r3057 | print('running lock') | |||
r1 | self.trylock() | |||
self.makelock(lockname, self.pidfile) | ||||
return True | ||||
def trylock(self): | ||||
running_pid = False | ||||
if self.debug: | ||||
r3057 | print('checking for already running process') | |||
r1 | try: | |||
with open(self.pidfile, 'r') as f: | ||||
try: | ||||
running_pid = int(f.readline()) | ||||
except ValueError: | ||||
running_pid = -1 | ||||
if self.debug: | ||||
r5021 | print(f'lock file present running_pid: {running_pid}, ' | |||
f'checking for execution') | ||||
r1 | # Now we check the PID from lock file matches to the current | |||
# process PID | ||||
if running_pid: | ||||
try: | ||||
r4928 | os.kill(running_pid, 0) | |||
r1 | except OSError as exc: | |||
if exc.errno in (errno.ESRCH, errno.EPERM): | ||||
r5021 | print("Lock File is there but the program is not running") | |||
print(f"Removing lock file for the: {running_pid}") | ||||
r1 | self.release() | |||
else: | ||||
raise | ||||
else: | ||||
r3057 | print("You already have an instance of the program running") | |||
r5021 | print(f"It is running as process {running_pid}") | |||
r1 | raise LockHeld() | |||
except IOError as e: | ||||
if e.errno != 2: | ||||
raise | ||||
def release(self): | ||||
"""releases the pid by removing the pidfile | ||||
""" | ||||
if self.debug: | ||||
r3057 | print('trying to release the pidlock') | |||
r1 | ||||
if self.callbackfn: | ||||
r3057 | # execute callback function on release | |||
r1 | if self.debug: | |||
r5021 | print(f'executing callback function {self.callbackfn}') | |||
r1 | self.callbackfn() | |||
try: | ||||
if self.debug: | ||||
r5021 | print(f'removing pidfile {self.pidfile}') | |||
r1 | os.remove(self.pidfile) | |||
self.held = False | ||||
except OSError as e: | ||||
if self.debug: | ||||
r5021 | print(f'removing pidfile failed {e}') | |||
r1 | pass | |||
def makelock(self, lockname, pidfile): | ||||
""" | ||||
this function will make an actual lock | ||||
:param lockname: acctual pid of file | ||||
:param pidfile: the file to write the pid in | ||||
""" | ||||
if self.debug: | ||||
r5021 | print(f'creating a file {lockname} and pid: {pidfile}') | |||
r1 | ||||
dir_, file_ = os.path.split(pidfile) | ||||
if not os.path.isdir(dir_): | ||||
os.makedirs(dir_) | ||||
with open(self.pidfile, 'wb') as f: | ||||
f.write(lockname) | ||||
self.held = True | ||||