Use shared locks for Fenrir instance coordination
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user