Ctrl-C can cause Windows Console IO reads to incorrectly return empty string
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 77.2k
- 分支
- 36k
- PR 合併指標
- PR 指標待擷取
描述
Bug report
Bug description:
Many file-like read functions are expected to return non-empty strings/bytes until EOF, eg the docs for io.RawIOBase here state:
If 0 bytes are returned, and size was not 0, this indicates end of file. If the object is in non-blocking mode and no bytes are available, None is returned.
However, on Windows, when reading from a console, Python will return 0 bytes on Ctrl-C if the default SIGINT handler is replaced with something that doesn't raise an exception, or presumably if you are reading from a thread other than the main thread.
To reproduce:
import signal, sys
signal.signal(signal.SIGINT, lambda *args: print('<SIGINT>', flush=True))
for i in range(10):
print(f"INPUT{i} = {sys.stdin.readline()!r}", flush=True)
Then run it in a console with various inputs:
C:\>py script1.py
line0
INPUT0 = 'line0\n'
line1
INPUT1 = 'line1\n'
<SIGINT>
INPUT2 = ''
line3
INPUT3 = 'line3\n'
line4<SIGINT>
INPUT4 = ''
line5
INPUT5 = 'line5\n'
^Z
INPUT6 = ''
^Zline7
INPUT7 = ''
line8
INPUT8 = 'line8\n'
line9
INPUT9 = 'line9\n'
As you can see, when CTRL-C is pressed, the custom SIGINT handler runs, doesn't raise KeyboardInterrupt, but readline() then returns an empty string. There is also similar behaviour for lines beginning with CTRL-Z, which appears to be intentional, and somewhat consistent with how cmd behaves, but since it doesn't actually close the underlying file handle (you can still read more data after encountering CTRL-Z) it might be surprising, and potentially worth documenting (though I'm not sure where, and it might be already somewhere that I didn't look 😛).
Just for reference, this doesn't affect reading from a pipe, for example, or presmably other file-like objects. Using the following script:
import signal, sys, time
signal.signal(signal.SIGINT, lambda *args: print('<CTRL-C>', file=sys.stderr, flush=True))
for i in range(5):
time.sleep(1)
print(f"test", flush=True)
C:\>py script2.py | py script1.py
INPUT0 = 'test0\n'
<CTRL-C>
<SIGINT>
INPUT1 = 'test1\n'
INPUT2 = 'test2\n'
<CTRL-C>
<CTRL-C>
<CTRL-C>
<SIGINT>
INPUT3 = 'test3\n'
<CTRL-C>
<SIGINT>
INPUT4 = 'test4\n'
INPUT5 = ''
INPUT6 = ''
INPUT7 = ''
INPUT8 = ''
INPUT9 = ''
Also interestingly in this case, it seems like the ctrl-c isn't delivered to the reading process till the next read completes (the writing process can receive and handle multiple interrupts between write operations). I'm not sure this behaviour is correct either, as it means there's no way to interrupt a read on a pipe that isn't receiving any data.
From what I can tell, the bug is in the read_console_w() function in _io/winconsoleio.c:
if (n == 0) {
err = GetLastError();
if (err != ERROR_OPERATION_ABORTED)
break;
err = 0;
HANDLE hInterruptEvent = _PyOS_SigintEvent();
if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
== WAIT_OBJECT_0) {
ResetEvent(hInterruptEvent);
Py_BLOCK_THREADS
sig = PyErr_CheckSignals();
Py_UNBLOCK_THREADS
if (sig < 0)
break;
}
}
*readlen += n;
/* If we didn't read a full buffer that time, don't try
again or we will block a second time. */
if (n < len)
break;
The code seems to check for Ctrl-C (signified by n == 0 && err == ERROR_OPERATION_ABORTED), then checks for a pending exception in a signal handler, but if there isn't one (sig == 0) it just falls through and then breaks out because n < len - with the comment about not blocking a second time. I think the solution would be to simply continue at the end of the n == 0 block, but I don't entirely know how the _PyOS_SigintEvent() thing works, and if it matters which side of that if-block you are.
CPython versions tested on:
3.12
Operating systems tested on:
Windows
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
從 Modules/_io/winconsoleio.c 中的 read_console_w() 開始,特別檢查 ERROR_OPERATION_ABORTED 的處理以及後續的短讀邏輯。使用自訂 SIGINT 處理常式執行所提供的 Windows 主控台重現,並比較變更前後的行為。當處理常式不引發例外時,Ctrl-C 不再使 readline() 回傳空字串,同時正常的 EOF 行為維持不變,即表示完成。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- c, python
- 領域
- operating-systems
- Issue 類型
- 缺陷
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 活躍度
- 停滯
- 描述清晰度
- 描述清楚
- 新手友好度
- 45/100