Use shared locks for Fenrir instance coordination
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import fcntl
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -34,22 +35,11 @@ class command:
|
|||||||
def _get_announcement_lock_path(self):
|
def _get_announcement_lock_path(self):
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
tempfile.gettempdir(),
|
tempfile.gettempdir(),
|
||||||
f"fenrirscreenreader-{os.getuid()}-time-announcement.lock",
|
"fenrirscreenreader-time-announcement.lock",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _try_create_announcement_lock(self, announcement_slot, now):
|
def _read_announcement_lock_slot(self, lock_file):
|
||||||
lock_path = self._get_announcement_lock_path()
|
lock_file.seek(0)
|
||||||
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()
|
lock_content = lock_file.readline().strip().split()
|
||||||
if len(lock_content) < 2:
|
if len(lock_content) < 2:
|
||||||
return ""
|
return ""
|
||||||
@@ -57,32 +47,33 @@ class command:
|
|||||||
|
|
||||||
def _claim_announcement_lock(self, announcement_slot):
|
def _claim_announcement_lock(self, announcement_slot):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if self._try_create_announcement_lock(announcement_slot, now):
|
|
||||||
return True
|
|
||||||
|
|
||||||
lock_path = self._get_announcement_lock_path()
|
lock_path = self._get_announcement_lock_path()
|
||||||
try:
|
try:
|
||||||
lock_slot = self._read_announcement_lock_slot(lock_path)
|
lock_fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o666)
|
||||||
lock_stat = os.stat(lock_path)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return self._try_create_announcement_lock(announcement_slot, now)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
try:
|
||||||
|
os.chmod(lock_path, 0o666)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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:
|
if lock_slot == announcement_slot:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not lock_slot and now - lock_stat.st_mtime < ANNOUNCEMENT_LOCK_TIMEOUT_SEC:
|
lock_file.seek(0)
|
||||||
return False
|
lock_file.truncate()
|
||||||
|
lock_file.write(f"{os.getpid()} {announcement_slot} {now}\n")
|
||||||
try:
|
lock_file.flush()
|
||||||
os.unlink(lock_path)
|
os.fsync(lock_file.fileno())
|
||||||
except FileNotFoundError:
|
return True
|
||||||
pass
|
finally:
|
||||||
except OSError:
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
||||||
return False
|
|
||||||
|
|
||||||
return self._try_create_announcement_lock(announcement_slot, now)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ command interrupt
|
|||||||
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import fcntl
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -38,7 +39,7 @@ from fenrirscreenreader.core.i18n import _
|
|||||||
|
|
||||||
|
|
||||||
REMOTE_COMMAND_LOCK_TIMEOUT_SEC = 2.0
|
REMOTE_COMMAND_LOCK_TIMEOUT_SEC = 2.0
|
||||||
REMOTE_COMMAND_LOCK_PREFIX = f"fenrirscreenreader-{os.getuid()}-remote-"
|
REMOTE_COMMAND_LOCK_PREFIX = "fenrirscreenreader-remote-"
|
||||||
|
|
||||||
|
|
||||||
class RemoteManager:
|
class RemoteManager:
|
||||||
@@ -304,47 +305,62 @@ class RemoteManager:
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
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:
|
try:
|
||||||
lock_fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
lock_pid = int(lock_parts[0]) if lock_parts else 0
|
||||||
except FileExistsError:
|
except ValueError:
|
||||||
return False
|
lock_pid = 0
|
||||||
|
return lock_pid
|
||||||
|
|
||||||
with os.fdopen(lock_fd, "w", encoding="utf-8") as lock_file:
|
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.write(f"{os.getpid()} {now}\n")
|
||||||
return True
|
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):
|
def _claim_remote_command(self, event_data):
|
||||||
lock_path = self._get_remote_command_lock_path(event_data)
|
lock_path = self._get_remote_command_lock_path(event_data)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
self._cleanup_stale_remote_command_locks(now)
|
self._cleanup_stale_remote_command_locks(now)
|
||||||
if self._try_create_remote_command_lock(lock_path, now):
|
lock_file = self._open_remote_command_lock(lock_path)
|
||||||
return True
|
if lock_file is None:
|
||||||
|
|
||||||
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):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
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 lock_pid == os.getpid():
|
if lock_pid == os.getpid():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if not lock_pid:
|
||||||
|
self._write_remote_command_lock(lock_file, now)
|
||||||
|
return True
|
||||||
|
|
||||||
if now - lock_stat.st_mtime < REMOTE_COMMAND_LOCK_TIMEOUT_SEC:
|
if now - lock_stat.st_mtime < REMOTE_COMMAND_LOCK_TIMEOUT_SEC:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
self._write_remote_command_lock(lock_file, now)
|
||||||
os.unlink(lock_path)
|
return True
|
||||||
except FileNotFoundError:
|
finally:
|
||||||
pass
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self._try_create_remote_command_lock(lock_path, now)
|
|
||||||
|
|
||||||
def list_instances(self):
|
def list_instances(self):
|
||||||
instances = remoteInstanceRegistry.list_instances()
|
instances = remoteInstanceRegistry.list_instances()
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2026.05.07"
|
version = "2026.05.08"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
|||||||
Reference in New Issue
Block a user