"""Один владелец файлового backend; timeout инвалидирует канал mutations.""" from __future__ import annotations import fcntl import json from pathlib import Path import threading import time import uuid from .build import write_json class BridgeError(RuntimeError): pass class FileBridge: def __init__(self, directory, session, timeout=5): self.directory = Path(directory) self.session = session self.timeout = timeout self.generation = None self.invalid = False self._mutex = threading.Lock() self._owner = (self.directory/'owner.lock').open('a') try: fcntl.flock(self._owner, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: self._owner.close() raise BridgeError('У backend уже есть управляющая сессия') def close(self): with self._mutex: self._owner.close() def request(self, command, **args): with self._mutex: if self._owner.closed: raise BridgeError('Канал закрыт') if self.invalid: raise BridgeError('Канал инвалидирован после timeout; требуется новая сессия') identity = str(uuid.uuid4().int) request = self.directory/f'req_{identity}.json' response = self.directory/f'resp_{identity}.json' write_json(request, {'session': self.session, 'generation': self.generation, 'command': command, 'args': args}) deadline = time.monotonic()+self.timeout while time.monotonic() < deadline: if response.exists(): try: value = json.loads(response.read_text()) if not isinstance(value, dict) or not {'session', 'generation', 'ok'} <= value.keys(): raise ValueError('Неверная структура ответа') except (OSError, ValueError) as error: self.invalid = True raise BridgeError('Повреждённый ответ backend: ' + str(error)) from error response.unlink() if value['session'] != self.session: self.invalid = True raise BridgeError('Ответ от другой сессии') self.generation = value['generation'] if not value['ok']: raise BridgeError(value['error']) return value['result'] time.sleep(.005) self.invalid = True request.unlink(missing_ok=True) raise BridgeError('Timeout: результат команды неизвестен; автоматический повтор запрещён') def wait_stopped(self, timeout=5): deadline = time.monotonic()+timeout while time.monotonic() < deadline: snapshot = self.request('snapshot') if snapshot['state']=='stopped': return snapshot if snapshot['state']=='invalidated': raise BridgeError('Сессия MAME инвалидирована reset/load') time.sleep(.005) raise BridgeError('CPU не остановился за отведённое время') def handshake(self): response = self.request('hello') if response['protocol'] != 1: raise BridgeError('Неподдержанная версия протокола') return response