diff --git a/src/fenrirscreenreader/commands/onHeartBeat/76000-time.py b/src/fenrirscreenreader/commands/onHeartBeat/76000-time.py index 87414661..5d3d459e 100755 --- a/src/fenrirscreenreader/commands/onHeartBeat/76000-time.py +++ b/src/fenrirscreenreader/commands/onHeartBeat/76000-time.py @@ -5,6 +5,7 @@ # By Chrys, Storm Dragon, and contributors. import datetime +import fcntl import os import tempfile import time @@ -34,55 +35,45 @@ class command: def _get_announcement_lock_path(self): return os.path.join( tempfile.gettempdir(), - f"fenrirscreenreader-{os.getuid()}-time-announcement.lock", + "fenrirscreenreader-time-announcement.lock", ) - def _try_create_announcement_lock(self, announcement_slot, now): - lock_path = self._get_announcement_lock_path() - try: - lock_fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - except FileExistsError: - return False - - with os.fdopen(lock_fd, "w", encoding="utf-8") as lock_file: - lock_file.write(f"{os.getpid()} {announcement_slot} {now}\n") - return True - - def _read_announcement_lock_slot(self, lock_path): - with open(lock_path, "r", encoding="utf-8") as lock_file: - lock_content = lock_file.readline().strip().split() + def _read_announcement_lock_slot(self, lock_file): + lock_file.seek(0) + lock_content = lock_file.readline().strip().split() if len(lock_content) < 2: return "" return lock_content[1] def _claim_announcement_lock(self, announcement_slot): now = time.time() - if self._try_create_announcement_lock(announcement_slot, now): - return True - lock_path = self._get_announcement_lock_path() try: - lock_slot = self._read_announcement_lock_slot(lock_path) - lock_stat = os.stat(lock_path) - except FileNotFoundError: - return self._try_create_announcement_lock(announcement_slot, now) + lock_fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o666) except OSError: return False - - if lock_slot == announcement_slot: - return False - - if not lock_slot and now - lock_stat.st_mtime < ANNOUNCEMENT_LOCK_TIMEOUT_SEC: - return False - try: - os.unlink(lock_path) - except FileNotFoundError: - pass + os.chmod(lock_path, 0o666) except OSError: - return False + pass - return self._try_create_announcement_lock(announcement_slot, now) + with os.fdopen(lock_fd, "r+", encoding="utf-8") as lock_file: + try: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) + lock_slot = self._read_announcement_lock_slot(lock_file) + lock_stat = os.fstat(lock_file.fileno()) + + if lock_slot == announcement_slot: + return False + + lock_file.seek(0) + lock_file.truncate() + lock_file.write(f"{os.getpid()} {announcement_slot} {now}\n") + lock_file.flush() + os.fsync(lock_file.fileno()) + return True + finally: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) def run(self): if not self.env["runtime"]["SettingsManager"].get_setting_as_bool( diff --git a/src/fenrirscreenreader/core/remoteManager.py b/src/fenrirscreenreader/core/remoteManager.py index ac96a4e2..d872cfe4 100644 --- a/src/fenrirscreenreader/core/remoteManager.py +++ b/src/fenrirscreenreader/core/remoteManager.py @@ -27,6 +27,7 @@ command interrupt import hashlib +import fcntl import os import tempfile import time @@ -38,7 +39,7 @@ from fenrirscreenreader.core.i18n import _ REMOTE_COMMAND_LOCK_TIMEOUT_SEC = 2.0 -REMOTE_COMMAND_LOCK_PREFIX = f"fenrirscreenreader-{os.getuid()}-remote-" +REMOTE_COMMAND_LOCK_PREFIX = "fenrirscreenreader-remote-" class RemoteManager: @@ -304,47 +305,62 @@ class RemoteManager: except OSError: pass - def _try_create_remote_command_lock(self, lock_path, now): + def _read_remote_command_lock(self, lock_file): + lock_file.seek(0) + lock_parts = lock_file.readline().strip().split() try: - lock_fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) - except FileExistsError: - return False + lock_pid = int(lock_parts[0]) if lock_parts else 0 + except ValueError: + lock_pid = 0 + return lock_pid - with os.fdopen(lock_fd, "w", encoding="utf-8") as lock_file: - lock_file.write(f"{os.getpid()} {now}\n") - return True + def _write_remote_command_lock(self, lock_file, now): + lock_file.seek(0) + lock_file.truncate() + lock_file.write(f"{os.getpid()} {now}\n") + lock_file.flush() + os.fsync(lock_file.fileno()) + + def _open_remote_command_lock(self, lock_path): + try: + lock_fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o666) + except OSError: + return None + try: + os.chmod(lock_path, 0o666) + except OSError: + pass + + return os.fdopen(lock_fd, "r+", encoding="utf-8") def _claim_remote_command(self, event_data): lock_path = self._get_remote_command_lock_path(event_data) now = time.time() self._cleanup_stale_remote_command_locks(now) - if self._try_create_remote_command_lock(lock_path, now): - return True - - try: - with open(lock_path, "r", encoding="utf-8") as lock_file: - lock_parts = lock_file.readline().strip().split() - lock_pid = int(lock_parts[0]) if lock_parts else 0 - lock_stat = os.stat(lock_path) - except FileNotFoundError: - return self._try_create_remote_command_lock(lock_path, now) - except (OSError, ValueError): + lock_file = self._open_remote_command_lock(lock_path) + if lock_file is None: return False - if lock_pid == os.getpid(): - return True + with lock_file: + try: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) + lock_pid = self._read_remote_command_lock(lock_file) + lock_stat = os.fstat(lock_file.fileno()) - if now - lock_stat.st_mtime < REMOTE_COMMAND_LOCK_TIMEOUT_SEC: - return False + if lock_pid == os.getpid(): + return True - try: - os.unlink(lock_path) - except FileNotFoundError: - pass - except OSError: - return False + if not lock_pid: + self._write_remote_command_lock(lock_file, now) + return True - return self._try_create_remote_command_lock(lock_path, now) + if now - lock_stat.st_mtime < REMOTE_COMMAND_LOCK_TIMEOUT_SEC: + return False + + self._write_remote_command_lock(lock_file, now) + return True + finally: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) def list_instances(self): instances = remoteInstanceRegistry.list_instances() diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index a877ff56..f0a3c583 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2026.05.07" +version = "2026.05.08" code_name = "testing"