Skip to content

Properly clean up response streams in BaseSession #515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 1, 2025

Conversation

bhosmer-ant
Copy link
Contributor

@bhosmer-ant bhosmer-ant commented Apr 15, 2025

Summary

  • Ensures that response streams are properly closed and removed from tracking dictionary by wrapping request handling in a try/finally block
  • Prevents memory leaks from response streams and their readers accumulating in the exit stack over time
  • Adds a test to verify proper cleanup when exceptions occur during requests
  • Adds a pytest warning filter needed for starlette 0.27 (pinned), without which tests fail

Test plan

  • Run the existing test suite
  • Added a specific unit test that verifies stream cleanup during exceptions
  • Manual testing with BaseSession and derived classes

May fix #169

Wraps the request handling in a try/finally block to ensure that response
streams are properly closed and removed from the tracking dictionary, even
if an exception occurs during request processing.

This change also prevents response_stream and response_stream_reader
instances from piling up on _exit_stack over the course of the session.

Github-Issue:#169
@bhosmer-ant bhosmer-ant requested review from dsp-ant, Kludex and ihrpr April 15, 2025 03:14
Comment on lines +269 to +270
await response_stream.aclose()
await response_stream_reader.aclose()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct tho? Do you want to close the stream on every request? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we're creating them on every request, so I think the old code was just piling
up open streams until the end of the session (or getting unclosed resource errors if they got __del__d before the session's __aexit__, or if the latter never ran).

Please check my logic though - I'm new to this code. E.g. is there a reason we need to leave streams open after getting the response?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but the connection stays open for some time after you send back the response.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, sounds like this would need a more sophisticated solution. I'll move this to draft and come back to it. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, it stays alive for 5 secs (https://www.uvicorn.org/settings/#timeouts) fyi

Copy link
Contributor Author

@bhosmer-ant bhosmer-ant Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kludex just to clarify - these are in-memory streams, not network streams. (_receive_loop gets the network response and writes it to response_stream). There may still be a problem I'm not aware of with closing the streams immediately, but want to make sure you weren't worried about closing while network connections were still open.

[edit: aha wrote this before seeing your fyi, so it sounds like you were worried about closing HTTPS streams, right? undrafting based on this assumption]

@bhosmer-ant bhosmer-ant marked this pull request as draft April 15, 2025 15:39
@bhosmer-ant bhosmer-ant marked this pull request as ready for review April 15, 2025 18:32
Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't upgrade starlette for now

@bhosmer-ant
Copy link
Contributor Author

@ihrpr thanks for taking a look! Backed out the starlette bump, but as a result needed to add a warning suppression to get tests to pass (they're failing on main as well for me, not sure why they haven't been blowing up for you?)

@bhosmer-ant bhosmer-ant requested a review from ihrpr April 29, 2025 19:56
@ihrpr ihrpr added this to the 2025-03-26 spec release milestone Apr 29, 2025
pyproject.toml Outdated
"ignore:Returning str or bytes.*:DeprecationWarning:mcp.server.lowlevel"
"ignore:Returning str or bytes.*:DeprecationWarning:mcp.server.lowlevel",
# this is a problem in starlette 0.27, which we're currently pinned to
"ignore:Please use `import python_multipart` instead.:PendingDeprecationWarning",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried running test without this change, all works for me, lets' remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@bhosmer-ant bhosmer-ant requested a review from ihrpr April 30, 2025 13:43
Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@@ -187,7 +187,6 @@ def __init__(
self._receive_notification_type = receive_notification_type
self._session_read_timeout_seconds = read_timeout_seconds
self._in_flight = {}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - is this intentional?

@ihrpr ihrpr merged commit 82bd8bc into main May 1, 2025
10 checks passed
@ihrpr ihrpr deleted the basil/resource_streams branch May 1, 2025 13:45
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.

Leaking STDIO client: Unclosed <MemoryObjectReceiveStream>
3 participants