Fix Implementation of Journal Locking

This commit is contained in:
David Sangrey committed 2026-06-01 23:18:49 -04:00
1 parent 31a19360c4
commit e897e4b022
2 files changed
+38 -30

No files matched your search

+36 -28
View File
@@ -55,6 +55,18 @@ class JournalLock:
except Exception: # pragma: no cover
logger.exception("Couldn't make pathlib.Path from journal_dir")
def open_journal_dir_lockfile(self) -> bool:
"""Open journal_dir lockfile ready for locking."""
self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' # type: ignore
logger.trace_if('journal-lock', f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}')
try:
self.lock = FileLock(self.journal_dir_lockfile_name)
return True
except Exception as e:
logger.warning(f"Couldn't prepare lockfile \"{self.journal_dir_lockfile_name}\". "
f"Aborting duplicate process checks: {e!r}")
return False
def obtain_lock(self) -> JournalLockResult:
"""
Attempt to obtain a lock on the journal directory.
@@ -64,45 +76,41 @@ class JournalLock:
if self.journal_dir_path is None:
return JournalLockResult.JOURNALDIR_IS_NONE
self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt'
logger.trace_if('journal-lock', f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}')
if not self.open_journal_dir_lockfile():
return JournalLockResult.JOURNALDIR_READONLY
return self._obtain_lock()
# Instantiate filelock engine (abstracts win32/fcntl natively)
self.lock = FileLock(self.journal_dir_lockfile_name)
def _obtain_lock(self) -> JournalLockResult:
"""
Obtain the lock.
:return: LockResult - See the class Enum definition
"""
if not self.lock:
return JournalLockResult.JOURNALDIR_READONLY
try:
# Write PID metadata into the lockfile for transparency
try:
with open(self.journal_dir_lockfile_name, mode='w', encoding='utf-8') as f:
f.write(f"Path: {self.journal_dir}\nPID: {os_getpid()}\n")
except (PermissionError, OSError):
try:
self.lock.acquire(timeout=0)
logger.trace_if('journal-lock', 'Done')
self.locked = True
return JournalLockResult.LOCKED
except Timeout:
logger.info(f"Couldn't lock journal directory \"{self.journal_dir}\","
f" assuming another process running.")
return JournalLockResult.ALREADY_LOCKED
except (PermissionError, OSError):
return JournalLockResult.JOURNALDIR_READONLY
# Immediately fail if another process holds the lock
self.lock.acquire(timeout=0)
logger.trace_if('journal-lock', 'Done')
self.locked = True
return JournalLockResult.LOCKED
except Timeout:
logger.info(f"Couldn't lock journal directory \"{self.journal_dir}\", assuming another process running.")
return JournalLockResult.ALREADY_LOCKED
except (PermissionError, OSError) as e:
logger.warning(f"Couldn't open/lock \"{self.journal_dir_lockfile_name}\". "
f"Aborting duplicate process checks: {e!r}")
# Catches read-only filesystems or directory permission denials natively via filelock
logger.warning(f"Couldn't acquire lock on \"{self.journal_dir_lockfile_name}\": {e!r}")
return JournalLockResult.JOURNALDIR_READONLY
# Write PID metadata into the lockfile after successful lock
try:
with open(self.journal_dir_lockfile_name, mode='w', encoding='utf-8') as f: # type: ignore
f.write(f"Path: {self.journal_dir}\nPID: {os_getpid()}\n")
except Exception as e:
# If writing metadata fails but the OS lock is held, log it but keep going
logger.warning(f"Lock acquired, but couldn't write PID metadata: {e!r}")
logger.trace_if('journal-lock', 'Done')
self.locked = True
return JournalLockResult.LOCKED
def release_lock(self) -> bool:
"""
Release lock on journal directory.
+2 -2
View File
@@ -13,7 +13,7 @@ import queue
import re
import sys
import threading
from calendar import timegm
from datetime import datetime
from collections import defaultdict
from os import SEEK_END, SEEK_SET, listdir
from os.path import basename, expanduser, getctime, isdir, join
@@ -620,7 +620,7 @@ class EDLogs(FileSystemEventHandler):
self.state['MarketID'] = None
self.state['StationType'] = None
self.stationservices = None
self.started = timegm(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ'))
self.started = int(datetime.fromisoformat(entry['timestamp']).timestamp())
# Don't set Ship, ShipID etc since this will reflect Fighter or SRV if starting in those
self.state.update({
'Captain': None,