Files
Sprinter-SDCC/toolchain/sdbg/transport.py
T

85 lines
3.6 KiB
Python

"""Один владелец файлового 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