Skip to content

fix: network task handles leak#3053

Closed
Gby56 wants to merge 1 commit intomicrosoft:mainfrom
Gby56:fix/network-task-leak
Closed

fix: network task handles leak#3053
Gby56 wants to merge 1 commit intomicrosoft:mainfrom
Gby56:fix/network-task-leak

Conversation

@Gby56
Copy link
Copy Markdown

@Gby56 Gby56 commented Apr 29, 2026

Hello 😄

Given several reports of memory leaks, and my personal experience of long-running contexts getting bloated after some time, leading to OOM problems, I wanted to investigate a bit with some help from Codex (and my personal investigation)

I added a failing test case to start

Findings

  • High confidence: playwright/_impl/_network.py:896 leaks a close-watcher task on normal completion. It creates on_finished_task, waits for either response-finished or page-close, but
    never cancels the page-close task when the response wins. I reproduced this with a local micro-check: normal finish leaves one pending close-watch task attached to the page close
    future.
  • High confidence: repeated browser_type.connect() can retain closed child connections. playwright/_impl/_browser_type.py:244 appends each child Connection to parent
    _child_ws_connections at playwright/_impl/_browser_type.py:291, but nothing removes it. playwright/_impl/_connection.py:327 also does not clear _objects, so a retained closed child can
    keep its object graph alive.
  • Medium confidence: disposed channel owners do not cancel pending async event tasks. playwright/_impl/_connection.py:177 removes protocol objects from maps, but does not cancel pyee
    pending futures or clear listeners. This is risky for long-running route/request handlers, because route events are scheduled as tasks in playwright/_impl/_page.py:287 and active route
    invocations retain routes in playwright/_impl/_helper.py:416. This matches the shape of GitHub issue [BUG] hang when node is crash or killed #1779 ([BUG] hang when node is crash or killed #1779).
  • Expected-but-important: APIResponse bodies from request.fetch() / route.fetch() stay in memory until explicit dispose or context close. The implementation only releases them via pla
    ywright/_impl/_fetch.py:516, and the official docs state the body remains in memory until dispose/context close: https://playwright.dev/python/docs/api/class-apiresponse. In a long-li
    ved context, missing await response.dispose() after route.fetch() is enough to look like a leak.
  • Lower confidence: tracing cleanup still has exception-path risks. The known tracing object leak [Bug]: Object leak in Tracing #2977 ([Bug]: Object leak in Tracing #2977) has a regression t
    est in tests/test_reference_count_async.py:78, but playwright/_impl/_tracing.py:68 increments global tracing state before tracing_started() completes, and playwright/_impl/_tracing.py
    :86 is not exception-safe around local zip/export cleanup. That lines up with trace-stop hang reports like [BUG] Hanging when browser runs out of memory #1847 ([BUG] Hanging when browser runs out of memory #1847).

@Gby56
Copy link
Copy Markdown
Author

Gby56 commented Apr 29, 2026

@microsoft-github-policy-service agree

@Skn0tt
Copy link
Copy Markdown
Member

Skn0tt commented Apr 30, 2026

We're not accepting failing test cases without a fix.

@Skn0tt Skn0tt closed this Apr 30, 2026
@Gby56
Copy link
Copy Markdown
Author

Gby56 commented Apr 30, 2026

@Skn0tt could i get at least a confirmation to whether the issue seems valid on your end, before i provide a fix ? I'm willing to help but I don't know all the internals here, I have just observed the problem regularly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants