Skip to content

A second Browser.stop()/quit() orphans a Connection.aclose coroutine (4.51.11) #4458

Description

@crippledgeek

Follow-up to the second-stop() behaviour I mentioned in #4454 — you asked for the minimal repro, so here it is.

Repro

import warnings

from seleniumbase import sb_cdp

sb = sb_cdp.Chrome("about:blank", headless=True)

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")

    sb.quit()
    print("after 1st quit:", [str(w.message)[:70] for w in caught])

    n = len(caught)
    sb.quit()
    print("after 2nd quit:", [str(w.message)[:70] for w in caught[n:]])

Run with python -W always repro.py.

Actual output

after 1st quit: []
after 2nd quit: ["coroutine 'Connection.aclose' was never awaited"]
.../asyncio/base_events.py:758: ResourceWarning: unclosed event loop <_UnixSelectorEventLoop running=False closed=False debug=False>

First quit() is clean. The second orphans the coroutine and abandons an event loop.

Expected

A second quit() is a no-op, as it was through 4.51.10.

Environment

SeleniumBase 4.51.11 (latest), Python 3.14.6, Linux. Re-run today against a clean install.

Where it comes from

Browser.quit() is self.stop(). In 4.51.11 stop() resolves a loop from a fallback chain (self.loopconnection.loopwebsocket._loop → policy → get_event_loop) instead of asyncio.get_running_loop(). On the second call that loop is already closed, so loop.run_until_complete(self.connection.aclose()) raises after the coroutine object has been constructed, and the except RuntimeError fallback at cdp_driver/browser.py:963 never awaits the first one. The pre-4.51.11 code raised before constructing it, which is why this never surfaced. The abandoned loop looks like it comes from the asyncio.set_event_loop(asyncio.new_event_loop()) in stop().

Why anything would call it twice

Fair question, and you're right that once is enough. In my case it was two independent teardown paths that could both fire — an explicit close, and a lifecycle path that also closes — with nothing between them saying "already done". That is my bug, and I've fixed it on my side with a latch so a second close never reaches the driver.

I'm raising it anyway because the change is a silent behavioural regression rather than a missing feature: the same double call was harmless through 4.51.10 and now leaks a coroutine and a loop. Anything with a finally:-plus-atexit shape will hit it, and the warning points at an internal frame rather than the caller's, so it isn't obvious where it came from.

Not urgent for me — happy for you to close it if you'd rather the contract be "call it once".

Metadata

Metadata

Assignees

Labels

UC Mode / CDP ModeUndetected Chromedriver Mode / CDP ModebugUh oh... Something needs to be fixed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions