projects
/
python_utils.git
/ blobdiff
commit
grep
author
committer
pickaxe
?
search:
re
summary
|
shortlog
|
log
|
commit
|
commitdiff
|
tree
raw
|
inline
| side by side
Cleanup
[python_utils.git]
/
lockfile.py
diff --git
a/lockfile.py
b/lockfile.py
index 03fbb9ef73a1449ffd8779a85ffe4ba885d193f6..f64a2c3aeba2eec62ea09246db2af44409a67d69 100644
(file)
--- a/
lockfile.py
+++ b/
lockfile.py
@@
-1,25
+1,28
@@
#!/usr/bin/env python3
#!/usr/bin/env python3
-from dataclasses import dataclass
+"""File-based locking helper."""
+
+from __future__ import annotations
+import contextlib
import datetime
import json
import logging
import os
import signal
import sys
import datetime
import json
import logging
import os
import signal
import sys
-from typing import Optional
import warnings
import warnings
+from dataclasses import dataclass
+from typing import Literal, Optional
import config
import datetime_utils
import decorator_utils
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,
cfg = config.add_commandline_args(f'Lockfile ({__file__})', 'Args related to lockfiles')
cfg.add_argument(
'--lockfile_held_duration_warning_threshold_sec',
type=float,
- default=
1
0.0,
+ default=
6
0.0,
metavar='SECONDS',
help='If a lock is held for longer than this threshold we log a warning',
)
metavar='SECONDS',
help='If a lock is held for longer than this threshold we log a warning',
)
@@
-27,17
+30,21
@@
logger = logging.getLogger(__name__)
class LockFileException(Exception):
class LockFileException(Exception):
+ """An exception related to lock files."""
+
pass
@dataclass
class LockFileContents:
pass
@dataclass
class LockFileContents:
+ """The contents we'll write to each lock file."""
+
pid: int
commandline: str
expiration_timestamp: Optional[float]
pid: int
commandline: str
expiration_timestamp: Optional[float]
-class LockFile(
object
):
+class LockFile(
contextlib.AbstractContextManager
):
"""A file locking mechanism that has context-manager support so you
can use it in a with statement. e.g.
"""A file locking mechanism that has context-manager support so you
can use it in a with statement. e.g.
@@
-57,9
+64,10
@@
class LockFile(object):
expiration_timestamp: Optional[float] = None,
override_command: Optional[str] = None,
) -> None:
expiration_timestamp: Optional[float] = None,
override_command: Optional[str] = None,
) -> None:
- self.is_locked = False
- self.lockfile = lockfile_path
- self.override_command = override_command
+ self.is_locked: bool = False
+ self.lockfile: str = lockfile_path
+ self.locktime: Optional[int] = None
+ self.override_command: Optional[str] = override_command
if do_signal_cleanup:
signal.signal(signal.SIGINT, self._signal)
signal.signal(signal.SIGTERM, self._signal)
if do_signal_cleanup:
signal.signal(signal.SIGINT, self._signal)
signal.signal(signal.SIGTERM, self._signal)
@@
-72,7
+80,7
@@
class LockFile(object):
return not os.path.exists(self.lockfile)
def try_acquire_lock_once(self) -> bool:
return not os.path.exists(self.lockfile)
def try_acquire_lock_once(self) -> bool:
- logger.debug(
f"Trying to acquire {self.lockfile}."
)
+ logger.debug(
"Trying to acquire %s.", self.lockfile
)
try:
# Attempt to create the lockfile. These flags cause
# os.open to raise an OSError if the file already
try:
# Attempt to create the lockfile. These flags cause
# os.open to raise an OSError if the file already
@@
-82,13
+90,12
@@
class LockFile(object):
contents = self._get_lockfile_contents()
logger.debug(contents)
f.write(contents)
contents = self._get_lockfile_contents()
logger.debug(contents)
f.write(contents)
- logger.debug(
f'Success; I own {self.lockfile}.'
)
+ logger.debug(
'Success; I own %s.', self.lockfile
)
self.is_locked = True
return True
except OSError:
pass
self.is_locked = True
return True
except OSError:
pass
- msg = f'Could not acquire {self.lockfile}.'
- logger.warning(msg)
+ logger.warning('Couldn\'t acquire %s.', self.lockfile)
return False
def acquire_with_retries(
return False
def acquire_with_retries(
@@
-126,19
+133,18
@@
class LockFile(object):
logger.warning(msg)
raise LockFileException(msg)
logger.warning(msg)
raise LockFileException(msg)
- def __exit__(self,
type, value, traceback)
:
+ def __exit__(self,
_, value, traceback) -> Literal[False]
:
if self.locktime:
ts = datetime.datetime.now().timestamp()
duration = ts - self.locktime
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)
+ if duration >= config.config['lockfile_held_duration_warning_threshold_sec']:
+ # Note: describe duration briefly only does 1s granularity...
+ str_duration = datetime_utils.describe_duration_briefly(int(duration))
msg = f'Held {self.lockfile} for {str_duration}'
logger.warning(msg)
warnings.warn(msg, stacklevel=2)
self.release()
msg = f'Held {self.lockfile} for {str_duration}'
logger.warning(msg)
warnings.warn(msg, stacklevel=2)
self.release()
+ return False
def __del__(self):
if self.is_locked:
def __del__(self):
if self.is_locked:
@@
-168,22
+174,27
@@
class LockFile(object):
line = lines[0]
line_dict = json.loads(line)
contents = LockFileContents(**line_dict)
line = lines[0]
line_dict = json.loads(line)
contents = LockFileContents(**line_dict)
- logger.debug(
f'Blocking lock contents="{contents}"'
)
+ logger.debug(
'Blocking lock contents="%s"', contents
)
# Does the PID exist still?
try:
os.kill(contents.pid, 0)
except OSError:
# Does the PID exist still?
try:
os.kill(contents.pid, 0)
except OSError:
- msg = f'Lockfile {self.lockfile}\'s pid ({contents.pid}) is stale; force acquiring'
- logger.warning(msg)
+ logger.warning(
+ 'Lockfile %s\'s pid (%d) is stale; force acquiring...',
+ self.lockfile,
+ contents.pid,
+ )
self.release()
# Has the lock expiration expired?
if contents.expiration_timestamp is not None:
now = datetime.datetime.now().timestamp()
if now > contents.expiration_timestamp:
self.release()
# Has the lock expiration expired?
if contents.expiration_timestamp is not None:
now = datetime.datetime.now().timestamp()
if now > contents.expiration_timestamp:
- msg = f'Lockfile {self.lockfile} expiration time has passed; force acquiring'
- logger.warning(msg)
+ logger.warning(
+ 'Lockfile %s\'s expiration time has passed; force acquiring',
+ self.lockfile,
+ )
self.release()
except Exception:
self.release()
except Exception:
- pass
+ pass
# If the lockfile doesn't exist or disappears, good.