Skip to content

make Stream.wait_closed wait for UDP client tasks - #330

Open
natefinch wants to merge 3 commits into
mitmproxy:mainfrom
natefinch:udp-wait-closed
Open

natefinch wants to merge 3 commits into
mitmproxy:mainfrom
natefinch:udp-wait-closed

Conversation

@natefinch

@natefinch natefinch commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Stream.wait_closed() is currently a no-op, so callers of open_udp_connection can't wait for the spawned UDP client task to finish. For example, it may still log after the Python side has torn down.

UDP client streams now hold a watch::Receiver whose sender is dropped when the task finishes, and wait_closed waits for that. Streams from servers are unchanged.

mitmproxy needs to await this after closing a stream. That change is in mitmproxy/mitmproxy#8456.

`Stream.wait_closed()` was a no-op, so callers of `open_udp_connection` could not wait for
the spawned UDP client task to finish. This matters when shutting down: the task may still
log after Python has moved on. Make `wait_closed` wait until the task has finished.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0822dee1-89da-4338-994f-bf1a11052c0e
@mhils

mhils commented Sep 30, 2026

Copy link
Copy Markdown
Member

I'm curious: What's the use case for waiting for the underlying UDP task to finish? I mean I understand the general appeal of wait_closed not being a no-op, but why specifically (only) UDP? :)

@natefinch

Copy link
Copy Markdown
Contributor Author

Good question! The concrete case is a segfault at shutdown. The task spawned by open_udp_connection can still be running when the process exits, and if it logs (e.g. "UDP client errored"), pyo3-log grabs the GIL during finalization and crashes. From a macOS crash report, the main thread is in exit() / OPENSSL_cleanup while the UDP task is in:

PyEval_RestoreThread                      <- SIGSEGV
PyGILState_Ensure
pyo3::internal::state::AttachGuard::do_attach_unchecked
<pyo3_log::Logger as log::Log>::log
mitmproxy_rs::udp_client::open_udp_connection::{{closure}}

It's UDP-only because that's the only Stream backed by its own task: server-side streams are covered by the server's wait_closed (and #329), and TCP client connections are plain asyncio streams. With this and mitmproxy/mitmproxy#8456, mitmproxy waits for the task before dropping the connection, so it can't outlive the interpreter.

natefinch and others added 2 commits October 2, 2026 12:32
Preserve both the UDP completion and server-closure regressions, initializing completion tracking for the upstream stream fixture.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cfacfee1-d3e5-4b4d-9b9f-2712008a29e6
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cfacfee1-d3e5-4b4d-9b9f-2712008a29e6
@mhils

mhils commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Ah, so this is vorner/pyo3-log#30 again. I really wonder if it's worth fixing this whack-a-mole, or if we should rather patch pyo3-log to not be a pain in the butt anymore (there's vorner/pyo3-log#78 for instance). Do you have any thoughts on that?

@natefinch

Copy link
Copy Markdown
Contributor Author

Sorry, missed this until now. I agree that there should be a fix to the late logging issue independent of this fix, but this fix is still an improvement to the code on its own, so that the wait actually waits, to avoid other possible issues where shutdown isn't clean.

This branch has not been deployed

No deployments
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