feat: enhance WebSocket error handling and cleanup logic in simulation and ESP32 libraries
parent
f5323aa557
commit
fdbc37b69b
|
|
@ -40,8 +40,14 @@ async def simulation_websocket(websocket: WebSocket, client_id: str):
|
|||
logger.info('[%s] system event: %s', client_id, data.get('event'))
|
||||
elif event_type == 'error':
|
||||
logger.error('[%s] error: %s', client_id, data.get('message'))
|
||||
elif event_type == 'serial_output':
|
||||
text = data.get('data', '')
|
||||
logger.info('[%s] serial_output uart=%s len=%d: %r', client_id, data.get('uart', 0), len(text), text[:80])
|
||||
payload = json.dumps({'type': event_type, 'data': data})
|
||||
await manager.send(client_id, payload)
|
||||
try:
|
||||
await manager.send(client_id, payload)
|
||||
except Exception as _send_exc:
|
||||
logger.debug('[%s] qemu_callback send failed (%s): %s', client_id, event_type, _send_exc)
|
||||
|
||||
def _use_lib() -> bool:
|
||||
return esp_lib_manager.is_available()
|
||||
|
|
@ -177,13 +183,19 @@ async def simulation_websocket(websocket: WebSocket, client_id: str):
|
|||
)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(client_id)
|
||||
qemu_manager.stop_instance(client_id)
|
||||
await esp_lib_manager.stop_instance(client_id)
|
||||
esp_qemu_manager.stop_instance(client_id)
|
||||
# Guard: only clean up if this coroutine still owns the connection for client_id.
|
||||
# A newer simulation_websocket may have already connected and replaced us.
|
||||
if manager.active_connections.get(client_id) is websocket:
|
||||
manager.disconnect(client_id)
|
||||
qemu_manager.stop_instance(client_id)
|
||||
await esp_lib_manager.stop_instance(client_id)
|
||||
esp_qemu_manager.stop_instance(client_id)
|
||||
else:
|
||||
logger.info('[%s] old WS session ended; newer session is active — skipping cleanup', client_id)
|
||||
except Exception as exc:
|
||||
logger.error('WebSocket error for %s: %s', client_id, exc)
|
||||
manager.disconnect(client_id)
|
||||
qemu_manager.stop_instance(client_id)
|
||||
await esp_lib_manager.stop_instance(client_id)
|
||||
esp_qemu_manager.stop_instance(client_id)
|
||||
if manager.active_connections.get(client_id) is websocket:
|
||||
manager.disconnect(client_id)
|
||||
qemu_manager.stop_instance(client_id)
|
||||
await esp_lib_manager.stop_instance(client_id)
|
||||
esp_qemu_manager.stop_instance(client_id)
|
||||
|
|
|
|||
|
|
@ -160,7 +160,10 @@ class EspLibManager:
|
|||
|
||||
logger.info('Launching esp32_worker for %s (machine=%s, script=%s, python=%s)',
|
||||
client_id, machine, _WORKER_SCRIPT, sys.executable)
|
||||
await callback('system', {'event': 'booting'})
|
||||
try:
|
||||
await callback('system', {'event': 'booting'})
|
||||
except Exception as exc:
|
||||
logger.warning('start_instance %s: booting event delivery failed: %s', client_id, exc)
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
|
|
@ -362,6 +365,17 @@ class EspLibManager:
|
|||
raw = raw.strip()
|
||||
if not raw:
|
||||
continue
|
||||
# With -nographic, serial0 is connected to the stdio mux so
|
||||
# qemu_chr_fe_write() writes the raw UART byte to fd 1 just
|
||||
# before picsimlab_uart_tx_event emits the JSON line. Strip
|
||||
# any prefix bytes before the JSON object marker.
|
||||
idx = raw.find(b'{"type":')
|
||||
if idx > 0:
|
||||
raw = raw[idx:]
|
||||
elif idx < 0:
|
||||
logger.debug('[%s] ignoring non-JSON worker line: %s',
|
||||
client_id, raw[:200])
|
||||
continue
|
||||
try:
|
||||
event = json.loads(raw)
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -307,6 +307,13 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
bridge.onCrash = () => {
|
||||
set({ esp32CrashBoardId: id });
|
||||
};
|
||||
bridge.onDisconnected = () => {
|
||||
set((s) => {
|
||||
const boards = s.boards.map((b) => b.id === id ? { ...b, running: false } : b);
|
||||
const isActive = s.activeBoardId === id;
|
||||
return { boards, ...(isActive ? { running: false } : {}) };
|
||||
});
|
||||
};
|
||||
bridge.onLedcUpdate = (update) => {
|
||||
// Route LEDC duty cycles to PinManager as PWM (0.0–1.0).
|
||||
// If gpio is known (from GPIO out_sel sync), use the actual GPIO pin;
|
||||
|
|
@ -619,6 +626,13 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
||||
};
|
||||
bridge.onCrash = () => { set({ esp32CrashBoardId: boardId }); };
|
||||
bridge.onDisconnected = () => {
|
||||
set((s) => {
|
||||
const boards = s.boards.map((b) => b.id === boardId ? { ...b, running: false } : b);
|
||||
const isActive = s.activeBoardId === boardId;
|
||||
return { boards, ...(isActive ? { running: false } : {}) };
|
||||
});
|
||||
};
|
||||
bridge.onLedcUpdate = (update) => {
|
||||
const boardPm = pinManagerMap.get(boardId);
|
||||
if (boardPm && typeof boardPm.updatePwm === 'function') {
|
||||
|
|
|
|||
Loading…
Reference in New Issue