# Copyright (C) 2010-2024 RhodeCode GmbH # # 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 . # # 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 # run the lock automatically ! 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: print('lock held finalazing and running lock.release()') lock.release() def lock(self): """ locking function, if lock is present it will raise LockHeld exception """ lockname = f'{os.getpid()}' if self.debug: print('running lock') self.trylock() self.makelock(lockname, self.pidfile) return True def trylock(self): running_pid = False if self.debug: print('checking for already running process') try: with open(self.pidfile, 'r') as f: try: running_pid = int(f.readline()) except ValueError: running_pid = -1 if self.debug: print(f'lock file present running_pid: {running_pid}, ' f'checking for execution') # Now we check the PID from lock file matches to the current # process PID if running_pid: try: os.kill(running_pid, 0) except OSError as exc: if exc.errno in (errno.ESRCH, errno.EPERM): print("Lock File is there but the program is not running") print(f"Removing lock file for the: {running_pid}") self.release() else: raise else: print("You already have an instance of the program running") print(f"It is running as process {running_pid}") raise LockHeld() except IOError as e: if e.errno != 2: raise def release(self): """releases the pid by removing the pidfile """ if self.debug: print('trying to release the pidlock') if self.callbackfn: # execute callback function on release if self.debug: print(f'executing callback function {self.callbackfn}') self.callbackfn() try: if self.debug: print(f'removing pidfile {self.pidfile}') os.remove(self.pidfile) self.held = False except OSError as e: if self.debug: print(f'removing pidfile failed {e}') 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: print(f'creating a file {lockname} and pid: {pidfile}') 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