Skip to content
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

Discard non-close messages in close timeout window #9508

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

lenard-mosys
Copy link

@lenard-mosys lenard-mosys commented Oct 18, 2024

What do these changes do?

Waits for a close message in the close timeout window, not just the first message.

Are there changes in behavior for the user?

Previously spurious abnormally closed websocket connections could be reported to the server, even if the client responded to the close message with a close in a timely manner.

Is it a substantial burden for the maintainers to support this?

No

Related issue number

#9506

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
    • It is a bugfix.
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
  • Add a new news fragment into the CHANGES/ folder

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Oct 18, 2024
@lenard-mosys
Copy link
Author

Breaks this test:

async def test_abnormal_closure_when_server_does_not_receive(
aiohttp_client: AiohttpClient,
) -> None:
"""Test abnormal closure when the server closes and a message is pending."""
async def handler(request: web.Request) -> web.WebSocketResponse:
ws = web.WebSocketResponse()
await ws.prepare(request)
await ws.close()
return ws
app = web.Application()
app.router.add_route("GET", "/", handler)
client = await aiohttp_client(app)
resp = await client.ws_connect("/")
await resp.send_str("some data")
await asyncio.sleep(0.1)
msg = await resp.receive()
assert msg.type is aiohttp.WSMsgType.CLOSE
assert resp.close_code == WSCloseCode.ABNORMAL_CLOSURE

IMO the test is broken, or at least names and comments are misleading. I'm a bit perplexed why this test even passed before. Note that the test tests for the close code of the ClientWebSocketResponse, not for the server's WebSocketResponse. The close code for ws within the handler should indeed be abnormal closure, but that's never tested.

I'm not entirely sure what this test intends to test, or even if it intends to test server or client side behavior.

@bdraco
Copy link
Member

bdraco commented Oct 18, 2024

I'm not entirely sure what this test intends to test, or even if it intends to test server or client side behavior.

The intent of the test is to verify the client gets an ABNORMAL_CLOSURE when sever initiated close while it was trying to send a str

@lenard-mosys
Copy link
Author

The intent of the test is to verify the client gets an ABNORMAL_CLOSURE when sever initiated close while it was trying to send a str

I think this is questionable. The server closed the connection cleanly, even if the client tried to send a message. There could be connection schemes where the client sends fire-and-forget status updates, and the server could close the connection whenever. The client should get the close code sent from the server, not abnormal closure in this case.

Having said that I struggle to find normative wording in the RFC that would describe the conforming behavior in this situation. The best I can find is (under "Normal Closure of Connections"):

https://datatracker.ietf.org/doc/html/rfc6455#section-7.3

Servers MAY close the WebSocket connection whenever desired.

And for 1006:

It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame.

The "e.g." doesn't help here, suggesting that there are other situations where this is applicable.

Anyway, I think there are some problems with the autoclose mechanism of the client, and I can make a similar test fail to have the expected result, even when the client doesn't try to send a message that the server doesn't expect.

@bdraco
Copy link
Member

bdraco commented Oct 18, 2024

Your changes seems reasonable to me. We likely need to adjust the test. I agree the RFC is not crystal clear.

We also need a test for the new behavior as well

@lenard-mosys
Copy link
Author

Your changes seems reasonable to me. We likely need to adjust the test. I agree the RFC is not crystal clear.

In any case, there is probably a missing test that checks for the server side behavior. This particular test can be made to pass with ws = web.WebSocketResponse(timeout=0.), which restores (roughly) the server-side behavior the client can test against, but I don't know how palatable that is.

@bdraco
Copy link
Member

bdraco commented Oct 18, 2024

Your changes seems reasonable to me. We likely need to adjust the test. I agree the RFC is not crystal clear.

In any case, there is probably a missing test that checks for the server side behavior. This particular test can be made to pass with ws = web.WebSocketResponse(timeout=0.), which restores (roughly) the server-side behavior the client can test against, but I don't know how palatable that is.

I think thats fine, but please add a comment to explain the 0 timeout.

Copy link

codecov bot commented Oct 21, 2024

Codecov Report

Attention: Patch coverage is 92.00000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 98.58%. Comparing base (68f9d5d) to head (a063ac0).
Report is 46 commits behind head on master.

Files with missing lines Patch % Lines
aiohttp/web_ws.py 87.50% 0 Missing and 1 partial ⚠️
tests/test_web_websocket_functional.py 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9508      +/-   ##
==========================================
- Coverage   98.58%   98.58%   -0.01%     
==========================================
  Files         107      105       -2     
  Lines       35012    35118     +106     
  Branches     4146     4182      +36     
==========================================
+ Hits        34517    34620     +103     
- Misses        330      332       +2     
- Partials      165      166       +1     
Flag Coverage Δ
CI-GHA 98.46% <92.00%> (-0.01%) ⬇️
OS-Linux 98.13% <92.00%> (-0.01%) ⬇️
OS-Windows 96.52% <94.11%> (-0.03%) ⬇️
OS-macOS 97.82% <92.00%> (+<0.01%) ⬆️
Py-3.10.11 97.69% <92.00%> (-0.01%) ⬇️
Py-3.10.15 97.62% <92.00%> (-0.01%) ⬇️
Py-3.11.10 97.68% <84.00%> (-0.02%) ⬇️
Py-3.11.9 97.76% <84.00%> (-0.02%) ⬇️
Py-3.12.7 98.18% <92.00%> (-0.01%) ⬇️
Py-3.13.0 98.16% <92.00%> (-0.01%) ⬇️
Py-3.9.13 97.59% <91.66%> (+<0.01%) ⬆️
Py-3.9.20 97.52% <91.66%> (-0.01%) ⬇️
Py-pypy7.3.16 97.15% <91.66%> (+<0.01%) ⬆️
VM-macos 97.82% <92.00%> (+<0.01%) ⬆️
VM-ubuntu 98.13% <92.00%> (-0.01%) ⬇️
VM-windows 96.52% <94.11%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bdraco bdraco added backport-3.10 Trigger automatic backporting to the 3.10 release branch by Patchback robot backport-3.11 Trigger automatic backporting to the 3.11 release branch by Patchback robot labels Oct 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-3.10 Trigger automatic backporting to the 3.10 release branch by Patchback robot backport-3.11 Trigger automatic backporting to the 3.11 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants