-
-
Notifications
You must be signed in to change notification settings - Fork 101
Prevent socket write buffer from being cleared when prematurely #822
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
base: main
Are you sure you want to change the base?
Changes from all commits
0855c8a
844bc51
5eaf1ac
d3e8258
d84185b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,3 +51,100 @@ def test_bytes_written(): | |
| wfile = makefile.MakeFile(sock, 'w') | ||
| wfile.write(b'bar') | ||
| assert wfile.bytes_written == 3 | ||
|
|
||
|
|
||
| class _RawWriteBlockOnce: | ||
| """Mock raw.write() returning None once, then writing normally.""" | ||
|
|
||
| def __init__(self): | ||
| """Initialize _RawWriteBlockOnce.""" | ||
| self.call_count = 0 | ||
| self.written = bytearray() | ||
|
|
||
| def __call__(self, chunk): | ||
| """Return None on first call to simulate a blocked write.""" | ||
| self.call_count += 1 | ||
| if self.call_count == 1: | ||
| return None | ||
| self.written.extend(chunk) | ||
| return len(chunk) | ||
|
|
||
| def fileno(self): | ||
| """Return a fake fd for select().""" | ||
| return -1 | ||
|
|
||
|
|
||
| class _RawWriteBlockAlways: | ||
| """Mock raw.write() that always returns None.""" | ||
|
|
||
| def __init__(self): | ||
| """Initialize _RawWriteBlockAlways.""" | ||
| self.call_count = 0 | ||
|
|
||
| def __call__(self, chunk): | ||
| """Return None to simulate a permanently blocked socket.""" | ||
| self.call_count += 1 | ||
|
|
||
| def fileno(self): | ||
| """Return a fake fd for select().""" | ||
| return -1 | ||
|
|
||
|
|
||
| def test_flush_recovers_from_temporary_block(monkeypatch): | ||
| """_flush_unlocked() retries after select when raw.write() returns None. | ||
|
|
||
| A temporarily blocked socket should recover once select() reports | ||
| the socket is writable again, delivering all buffered data. | ||
| """ | ||
| data = b'x' * (makefile.SOCK_WRITE_BLOCKSIZE * 2) | ||
|
|
||
| sock = MockSocket() | ||
| wfile = makefile.MakeFile(sock, 'w') | ||
| wfile._write_buf.extend(data) | ||
|
|
||
| mock = _RawWriteBlockOnce() | ||
| wfile.raw.write = mock | ||
|
|
||
| # select() reports writable immediately | ||
| monkeypatch.setattr( | ||
| 'cheroot.makefile.select.select', | ||
| lambda _rlist, wlist, _xlist, _timeout: ([], wlist, []), | ||
| ) | ||
| wfile._flush_unlocked() | ||
|
|
||
| assert bytes(mock.written) == data, ( | ||
| 'all buffered data should be written after select retry' | ||
| ) | ||
|
|
||
|
|
||
| def test_flush_raises_on_sustained_block(monkeypatch): | ||
| """_flush_unlocked() raises BlockingIOError after select timeout. | ||
|
|
||
| If the socket stays blocked past SOCK_WRITE_TIMEOUT, the write | ||
| buffer must be preserved and BlockingIOError raised. | ||
| """ | ||
| import io | ||
|
|
||
| import pytest | ||
|
Comment on lines
+126
to
+128
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imports should be placed at the top of the file |
||
|
|
||
| data = b'x' * makefile.SOCK_WRITE_BLOCKSIZE | ||
|
|
||
| sock = MockSocket() | ||
| wfile = makefile.MakeFile(sock, 'w') | ||
| wfile._write_buf.extend(data) | ||
|
|
||
| mock = _RawWriteBlockAlways() | ||
| wfile.raw.write = mock | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to patch instance methods using
|
||
|
|
||
| # select() reports not writable (timeout) | ||
| monkeypatch.setattr( | ||
| 'cheroot.makefile.select.select', | ||
| lambda _rlist, _wlist, _xlist, _timeout: ([], [], []), | ||
| ) | ||
|
|
||
| with pytest.raises(io.BlockingIOError): | ||
| wfile._flush_unlocked() | ||
|
|
||
| assert len(wfile._write_buf) == len(data), ( | ||
| 'write buffer must be preserved when socket stays blocked' | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Fixed a bug that could cause premature clearing of the write buffer when a socket write is blocked. | ||
|
|
||
| -- by :user:`cbbm142` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarity, I would add an explicit
return None