From 563b23a33726289387193b85d90fb569631ee05f Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 23:52:22 +0300 Subject: Reconnect to a restarted Wayland compositor Windows now outlive the compositor. On connection loss YASL sends DISPLAY_LOST instead of closing every window, retries the connection on a loop timer, rebuilds each toplevel with its title, size, state and GL context, and sends DISPLAY_RESTORED. CPU windows keep their pixels; GPU renderers drop Vulkan surfaces and GL objects on loss and rebuild them on restore. - Free the dead connection's objects on the next wait, after the application has released GPU objects built on them. - Report a lost connection as an event rather than a failed wait. - Refuse window and Vulkan surface creation while offline with the new YASL_ERROR_DISCONNECTED. - Close popups, cancel drags and fail pending clipboard requests on loss. - Print only protocol errors, not ordinary disconnects. - Examples survive a restart; game.c rebinds its GL context. - Add an end-to-end test that kills Weston under CPU, GL and Vulkan windows and requires them to recover. Co-Authored-By: Claude Opus 5.5 --- tests/helpers/run_reconnect.py | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/helpers/run_reconnect.py (limited to 'tests/helpers/run_reconnect.py') diff --git a/tests/helpers/run_reconnect.py b/tests/helpers/run_reconnect.py new file mode 100644 index 0000000..e86e994 --- /dev/null +++ b/tests/helpers/run_reconnect.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Kill Weston under a running client, start a new one on the same socket, and +require the client to reconnect: run_reconnect.py .""" +import os +from pathlib import Path +import signal +import subprocess +import sys +import tempfile +import time + + +def start_weston(weston, root, env, log): + process = subprocess.Popen([weston, '--backend=headless', '--renderer=gl', '--socket=yasl-test', + '--idle-time=0', '--no-config', '--width=1024', '--height=768'], + env=env, stdout=log, stderr=log) + deadline = time.monotonic() + 12 + while not (root/'yasl-test').is_socket(): + if process.poll() is not None or time.monotonic() > deadline: + raise RuntimeError('Weston did not start') + time.sleep(0.02) + return process + + +def expect(client, word): + line = client.stdout.readline().strip() + if line != word: + raise RuntimeError(f'client printed {line!r}, expected {word!r}') + + +def main(): + weston, binary = sys.argv[1:] + signal.alarm(50) + with tempfile.TemporaryDirectory(prefix='yasl-reconnect-') as directory: + root = Path(directory) + env = os.environ.copy() + env.pop('WAYLAND_SOCKET', None) + env.update(XDG_RUNTIME_DIR=directory, WAYLAND_DISPLAY='yasl-test', LIBGL_ALWAYS_SOFTWARE='1') + lavapipe = next(Path('/usr/share/vulkan/icd.d').glob('*lvp*.json'), None) + if lavapipe: + env['VK_DRIVER_FILES'] = str(lavapipe) + with (root/'weston.log').open('w+') as log: + servers = [start_weston(weston, root, env, log)] + client = subprocess.Popen([binary], env=env, stdout=subprocess.PIPE, text=True) + try: + expect(client, 'ready') + servers[0].send_signal(signal.SIGKILL) + servers[0].wait() + expect(client, 'lost') + time.sleep(0.5) + servers.append(start_weston(weston, root, env, log)) + expect(client, 'restored') + if client.wait(timeout=15) != 0: + raise RuntimeError(f'client exited with {client.returncode}') + except BaseException: + client.kill() + log.flush(); log.seek(0); print(log.read(), file=sys.stderr) + raise + finally: + for server in servers: + if server.poll() is None: + server.terminate() + server.wait(timeout=5) + + +if __name__ == '__main__': + main() -- cgit v1.2.3