X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=lockfile.py;h=38134b20d02f00f8d419b5ddfef669c5002a4abf;hb=31c81f6539969a5eba864d3305f9fb7bf716a367;hp=1e0516bf75a340b4a15629141cdfbfb83fc3485c;hpb=709370b2198e09f1dbe195fe8813602a3125b7f6;p=python_utils.git diff --git a/lockfile.py b/lockfile.py index 1e0516b..38134b2 100644 --- a/lockfile.py +++ b/lockfile.py @@ -1,17 +1,27 @@ #!/usr/bin/env python3 -from dataclasses import dataclass import datetime import json import logging import os import signal import sys +import warnings +from dataclasses import dataclass from typing import Optional +import config +import datetime_utils import decorator_utils - +cfg = config.add_commandline_args(f'Lockfile ({__file__})', 'Args related to lockfiles') +cfg.add_argument( + '--lockfile_held_duration_warning_threshold_sec', + type=float, + default=10.0, + metavar='SECONDS', + help='If a lock is held for longer than this threshold we log a warning', +) logger = logging.getLogger(__name__) @@ -23,7 +33,7 @@ class LockFileException(Exception): class LockFileContents: pid: int commandline: str - expiration_timestamp: float + expiration_timestamp: Optional[float] class LockFile(object): @@ -39,12 +49,12 @@ class LockFile(object): """ def __init__( - self, - lockfile_path: str, - *, - do_signal_cleanup: bool = True, - expiration_timestamp: Optional[float] = None, - override_command: Optional[str] = None, + self, + lockfile_path: str, + *, + do_signal_cleanup: bool = True, + expiration_timestamp: Optional[float] = None, + override_command: Optional[str] = None, ) -> None: self.is_locked = False self.lockfile = lockfile_path @@ -76,20 +86,20 @@ class LockFile(object): return True except OSError: pass - logger.debug(f'Failed; I could not acquire {self.lockfile}.') + msg = f'Could not acquire {self.lockfile}.' + logger.warning(msg) return False def acquire_with_retries( - self, - *, - initial_delay: float = 1.0, - backoff_factor: float = 2.0, - max_attempts = 5 + self, + *, + initial_delay: float = 1.0, + backoff_factor: float = 2.0, + max_attempts=5, ) -> bool: - - @decorator_utils.retry_if_false(tries = max_attempts, - delay_sec = initial_delay, - backoff = backoff_factor) + @decorator_utils.retry_if_false( + tries=max_attempts, delay_sec=initial_delay, backoff=backoff_factor + ) def _try_acquire_lock_with_retries() -> bool: success = self.try_acquire_lock_once() if not success and os.path.exists(self.lockfile): @@ -109,12 +119,24 @@ class LockFile(object): def __enter__(self): if self.acquire_with_retries(): + self.locktime = datetime.datetime.now().timestamp() return self msg = f"Couldn't acquire {self.lockfile}; giving up." logger.warning(msg) raise LockFileException(msg) def __exit__(self, type, value, traceback): + if self.locktime: + ts = datetime.datetime.now().timestamp() + duration = ts - self.locktime + if ( + duration + >= config.config['lockfile_held_duration_warning_threshold_sec'] + ): + str_duration = datetime_utils.describe_duration_briefly(duration) + msg = f'Held {self.lockfile} for {str_duration}' + logger.warning(msg) + warnings.warn(msg, stacklevel=2) self.release() def __del__(self): @@ -130,11 +152,10 @@ class LockFile(object): cmd = self.override_command else: cmd = ' '.join(sys.argv) - print(cmd) contents = LockFileContents( - pid = os.getpid(), - commandline = cmd, - expiration_timestamp = self.expiration_timestamp, + pid=os.getpid(), + commandline=cmd, + expiration_timestamp=self.expiration_timestamp, ) return json.dumps(contents.__dict__) @@ -152,15 +173,16 @@ class LockFile(object): try: os.kill(contents.pid, 0) except OSError: - logger.debug('The pid seems stale; killing the lock.') + msg = f'Lockfile {self.lockfile}\'s pid ({contents.pid}) is stale; force acquiring' + logger.warning(msg) self.release() # Has the lock expiration expired? if contents.expiration_timestamp is not None: now = datetime.datetime.now().timestamp() - if now > contents.expiration_datetime: - logger.debug('The expiration time has passed; ' + - 'killing the lock') + if now > contents.expiration_timestamp: + msg = f'Lockfile {self.lockfile} expiration time has passed; force acquiring' + logger.warning(msg) self.release() except Exception: pass