You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Sync API — pw.stop() raises "This event loop is already running" at teardown after 1.64 sync-dispatcher rework (#3230) #3307
playwright 1.64.0 and 1.64.1, Python 3.12, Linux, sync API under pytest + pytest-xdist.
Summary
After upgrading 1.59 → 1.64, tearing down a session-scoped sync Playwright intermittently raises RuntimeError: This event loop is already running from pw.stop(). The user code completed successfully; only teardown fails.
Connection.stop_sync() is byte-identical between 1.59 and 1.64. The behavioural change is in sync_api/_context_manager.py::greenlet_main + the new _impl/_greenlets.py::wait_for_future helper introduced in #3230. At teardown the dispatcher fiber's run_until_complete(task) can still be on the stack (so loop._thread_id is still set) when stop_sync() runs its own run_until_complete(...), so _check_running() trips.
Intermittent, markedly more frequent against slow / high-latency backends.
Not triggered by a signal or a timeout: it occurs with pytest-timeout using --timeout-method=thread and the test finishing well under the limit, so no signal reaches the main thread. (This is distinct from the signal-handler scenario fix(sync): survive an exception raised by a signal handler during a sync call #3230's title describes.)
We don't yet have a deterministic standalone minimal repro (it's timing-dependent), but it reproduces at a high rate on slower environments. Happy to test a proposed fix.
Question
Is this a known regression from #3230? What is the intended teardown contract when the dispatcher fiber is still live at stop_sync()?
Hi, I'm the Playwright bot. I reproduced this and prepared a fix.
Confirmed regression from #3230. On Linux with Python 3.12.15, a completed event wait leaves a queued callback that resumes user code during pw.stop(), before the dispatcher exits. No signal or xdist is needed. The fix makes that callback inactive when its wait ends, including callbacks already queued by asyncio.
The same script passes on 1.63.0 (Chromium 153.0.8010.12) and fails on both 1.64.0 and 1.64.1 (Chromium 156.0.8078.4), at the reported Connection.stop_sync() call.
On main at 0fcb2ee2eb87cc630ff079208199afd7205d6fb8, with driver commit eac236da288cdc3d0891278a61e1a876348950a0, the regression test fails on Chromium, Firefox and WebKit. The newer shutdown path raises the same error at shutdown_asyncgens().
The test passes on all three browsers with the fix. The neighbouring sync checks pass under three xdist workers: 125 passed, one existing upstream skip. The corresponding async checks pass: 62 passed, the same skip. mypy playwright and ./scripts/lint.sh pass.
stop() must let the dispatcher finish before it runs loop cleanup. The fault is in the callback lifetime in wait_for_future(), not in Connection.stop_sync() or pytest-timeout.
Activity
github-actions commentedon Nov 3, 2026
microsoft-playwright-automation commentedon Nov 3, 2026
Hi, I'm the Playwright bot. I reproduced this and prepared a fix.
Confirmed regression from #3230. On Linux with Python 3.12.15, a completed event wait leaves a queued callback that resumes user code during
pw.stop(), before the dispatcher exits. No signal or xdist is needed. The fix makes that callback inactive when its wait ends, including callbacks already queued by asyncio.Minimal repro and verification
The same script passes on 1.63.0 (Chromium 153.0.8010.12) and fails on both 1.64.0 and 1.64.1 (Chromium 156.0.8078.4), at the reported
Connection.stop_sync()call.On main at
0fcb2ee2eb87cc630ff079208199afd7205d6fb8, with driver commiteac236da288cdc3d0891278a61e1a876348950a0, the regression test fails on Chromium, Firefox and WebKit. The newer shutdown path raises the same error atshutdown_asyncgens().The test passes on all three browsers with the fix. The neighbouring sync checks pass under three xdist workers: 125 passed, one existing upstream skip. The corresponding async checks pass: 62 passed, the same skip.
mypy playwrightand./scripts/lint.shpass.stop()must let the dispatcher finish before it runs loop cleanup. The fault is in the callback lifetime inwait_for_future(), not inConnection.stop_sync()or pytest-timeout.Draft PR: #3311
Triaged on Linux by the Playwright bot - agent run