async def test_ipc_server(tmpdir, monkeypatch): # The dummy directory should be automatically created when the server starts file1 = Path(tmpdir / "dummy" / "1.lock") mut1 = uuid4().hex async def _cmd_handler(cmd): assert cmd == {"cmd": IPCCommand.FOREGROUND} return {"status": "ok"} mut1 = uuid4().hex async with real_clock_timeout(): async with run_ipc_server(_cmd_handler, socket_file=file1, win32_mutex_name=mut1): with pytest.raises(IPCServerAlreadyRunning): async with run_ipc_server(_cmd_handler, socket_file=file1, win32_mutex_name=mut1): pass # Send good command ret = await send_to_ipc_server(file1, IPCCommand.FOREGROUND) assert ret == {"status": "ok"} # Send bad command, should be catched before even trying to reach the server with pytest.raises(IPCServerError) as exc: ret = await send_to_ipc_server(file1, "dummy") assert str(exc.value).startswith("Invalid message format:") # Force bad command to reach the server monkeypatch.setattr( "parsec.core.ipcinterface.cmd_req_serializer.dump", lambda x: x) with pytest.raises(IPCServerBadResponse) as exc: await send_to_ipc_server(file1, "dummy") assert exc.value.rep == { "status": "invalid_format", "reason": "{'cmd': ['Unsupported value: dummy']}", }
async def _run_ipc_server(config, main_window, start_arg, task_status=trio.TASK_STATUS_IGNORED): new_instance_needed = main_window.new_instance_needed foreground_needed = main_window.foreground_needed async def _cmd_handler(cmd): if cmd["cmd"] == IPCCommand.FOREGROUND: foreground_needed.emit() elif cmd["cmd"] == IPCCommand.NEW_INSTANCE: new_instance_needed.emit(cmd.get("start_arg")) return {"status": "ok"} # Loop over attemps at running an IPC server or sending the command to an existing one while True: # Attempt to run an IPC server if Parsec is not already started try: async with run_ipc_server( _cmd_handler, config.ipc_socket_file, win32_mutex_name=config.ipc_win32_mutex_name): task_status.started() await trio.sleep_forever() # Parsec is already started, give it our work then except IPCServerAlreadyRunning: # Protect against race conditions, in case the server was shutting down try: if start_arg: await send_to_ipc_server(config.ipc_socket_file, IPCCommand.NEW_INSTANCE, start_arg=start_arg) else: await send_to_ipc_server(config.ipc_socket_file, IPCCommand.FOREGROUND) # IPC server has closed, retry to create our own except IPCServerNotRunning: continue # We have successfuly noticed the other running application # We can now forward the exception to the caller raise
async def _start_ipc_server(config, main_window, start_arg, result_queue): new_instance_needed_qt = ThreadSafeQtSignal(main_window, "new_instance_needed", object) foreground_needed_qt = ThreadSafeQtSignal(main_window, "foreground_needed") async def cmd_handler(cmd): if cmd["cmd"] == "foreground": foreground_needed_qt.emit() elif cmd["cmd"] == "new_instance": new_instance_needed_qt.emit(cmd.get("start_arg")) return {"status": "ok"} while True: try: async with run_ipc_server( cmd_handler, config.ipc_socket_file, win32_mutex_name=config.ipc_win32_mutex_name): result_queue.put("started") await trio.sleep_forever() except IPCServerAlreadyRunning: # Parsec is already started, give it our work then try: try: if start_arg: await send_to_ipc_server(config.ipc_socket_file, "new_instance", start_arg=start_arg) else: await send_to_ipc_server(config.ipc_socket_file, "foreground") finally: result_queue.put("already_running") return except IPCServerNotRunning: # IPC server has closed, retry to create our own continue