From 0560843b8fe8d4dd9e830f7b3ae7984154914516 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 9 Jan 2009 18:53:14 +0000 Subject: Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line flag to work properly. Furthermore, when specifying -u, the text stdout and stderr streams have line-by-line buffering enabled (the default being to buffer arbitrary chunks of data). Patch by Victor Stinner, test by me. --- Include/pydebug.h | 1 + Lib/test/test_cmd_line.py | 17 ++++++++++ Misc/NEWS | 5 +++ Modules/main.c | 7 ++-- Python/pythonrun.c | 85 ++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 102 insertions(+), 13 deletions(-) diff --git a/Include/pydebug.h b/Include/pydebug.h index cd56196..0a31f5b 100644 --- a/Include/pydebug.h +++ b/Include/pydebug.h @@ -18,6 +18,7 @@ PyAPI_DATA(int) Py_IgnoreEnvironmentFlag; PyAPI_DATA(int) Py_DivisionWarningFlag; PyAPI_DATA(int) Py_DontWriteBytecodeFlag; PyAPI_DATA(int) Py_NoUserSiteDirectory; +PyAPI_DATA(int) Py_UnbufferedStdioFlag; /* this is a wrapper around getenv() that pays attention to Py_IgnoreEnvironmentFlag. It should be used for getting variables like diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index e8db7d0..e567184 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -142,6 +142,23 @@ class CmdLineTest(unittest.TestCase): self.exit_code('-c', command), 0) + def test_unbuffered_output(self): + # Test expected operation of the '-u' switch + for stream in ('stdout', 'stderr'): + # Binary is unbuffered + code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)" + % stream) + data, rc = self.start_python_and_exit_code('-u', '-c', code) + self.assertEqual(rc, 0) + self.assertEqual(data, b'x', "binary %s not unbuffered" % stream) + # Text is line-buffered + code = ("import os, sys; sys.%s.write('x\\n'); os._exit(0)" + % stream) + data, rc = self.start_python_and_exit_code('-u', '-c', code) + self.assertEqual(rc, 0) + self.assertEqual(data.strip(), b'x', + "text %s not line-buffered" % stream) + def test_main(): test.support.run_unittest(CmdLineTest) diff --git a/Misc/NEWS b/Misc/NEWS index 0ce7f7a..f7e126f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,11 @@ What's New in Python 3.1 alpha 0 Core and Builtins ----------------- +- Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line + flag to work properly. Furthermore, when specifying -u, the text stdout + and stderr streams have line-by-line buffering enabled (the default being + to buffer arbitrary chunks of data). + - The internal table, _PyLong_DigitValue, is now an array of unsigned chars instead of ints (reducing its size from 4 to 8 times thereby reducing Python's overall memory). diff --git a/Modules/main.c b/Modules/main.c index 3025d09..6de1523 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -292,7 +292,6 @@ Py_Main(int argc, wchar_t **argv) wchar_t *module = NULL; FILE *fp = stdin; char *p; - int unbuffered = 0; int skipfirstline = 0; int stdin_is_interactive = 0; int help = 0; @@ -374,7 +373,7 @@ Py_Main(int argc, wchar_t **argv) break; case 'u': - unbuffered++; + Py_UnbufferedStdioFlag = 1; saw_unbuffered_flag = 1; break; @@ -423,7 +422,7 @@ Py_Main(int argc, wchar_t **argv) Py_InspectFlag = 1; if (!saw_unbuffered_flag && (p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') - unbuffered = 1; + Py_UnbufferedStdioFlag = 1; if (!Py_NoUserSiteDirectory && (p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0') @@ -444,7 +443,7 @@ Py_Main(int argc, wchar_t **argv) stdin_is_interactive = Py_FdIsInteractive(stdin, (char *)0); - if (unbuffered) { + if (Py_UnbufferedStdioFlag) { #if defined(MS_WINDOWS) || defined(__CYGWIN__) _setmode(fileno(stdin), O_BINARY); _setmode(fileno(stdout), O_BINARY); diff --git a/Python/pythonrun.c b/Python/pythonrun.c index c5f7e23..8a1af3b 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -88,6 +88,7 @@ int Py_UseClassExceptionsFlag = 1; /* Needed by bltinmodule.c: deprecated */ int Py_FrozenFlag; /* Needed by getpath.c */ int Py_IgnoreEnvironmentFlag; /* e.g. PYTHONPATH, PYTHONHOME */ int Py_NoUserSiteDirectory = 0; /* for -s and site.py */ +int Py_UnbufferedStdioFlag = 0; /* Unbuffered binary std{in,out,err} */ /* PyModule_GetWarningsModule is no longer necessary as of 2.6 since _warnings is builtin. This API should not be used. */ @@ -728,6 +729,75 @@ initsite(void) } } +static PyObject* +create_stdio(PyObject* io, + int fd, int write_mode, char* name, + char* encoding, char* errors) +{ + PyObject *buf = NULL, *stream = NULL, *text = NULL, *raw = NULL; + const char* mode; + const PyObject *line_buffering; + int buffering; + + if (Py_UnbufferedStdioFlag) + buffering = 0; + else + buffering = -1; + if (write_mode) + mode = "wb"; + else + mode = "rb"; + buf = PyObject_CallMethod(io, "open", "isiOOOi", + fd, mode, buffering, + Py_None, Py_None, Py_None, 0); + if (buf == NULL) + goto error; + + if (!Py_UnbufferedStdioFlag) { + raw = PyObject_GetAttrString(buf, "raw"); + if (raw == NULL) + goto error; + } + else { + raw = buf; + Py_INCREF(raw); + } + + text = PyUnicode_FromString(name); + if (text == NULL || PyObject_SetAttrString(raw, "_name", text) < 0) + goto error; + Py_CLEAR(raw); + Py_CLEAR(text); + + if (Py_UnbufferedStdioFlag) + line_buffering = Py_True; + else + line_buffering = Py_False; + stream = PyObject_CallMethod(io, "TextIOWrapper", "OsssO", + buf, encoding, errors, + "\n", line_buffering); + Py_CLEAR(buf); + if (stream == NULL) + goto error; + + if (write_mode) + mode = "w"; + else + mode = "r"; + text = PyUnicode_FromString(mode); + if (!text || PyObject_SetAttrString(stream, "mode", text) < 0) + goto error; + Py_CLEAR(text); + return stream; + +error: + Py_XDECREF(buf); + Py_XDECREF(stream); + Py_XDECREF(text); + Py_XDECREF(raw); + return NULL; +} + /* Initialize sys.stdin, stdout, stderr and builtins.open */ static int initstdio(void) @@ -794,10 +864,9 @@ initstdio(void) #endif } else { - if (!(std = PyFile_FromFd(fd, "", "r", -1, encoding, - errors, "\n", 0))) { + std = create_stdio(iomod, fd, 0, "", encoding, errors); + if (std == NULL) goto error; - } } /* if (fd < 0) */ PySys_SetObject("__stdin__", std); PySys_SetObject("stdin", std); @@ -814,10 +883,9 @@ initstdio(void) #endif } else { - if (!(std = PyFile_FromFd(fd, "", "w", -1, encoding, - errors, "\n", 0))) { + std = create_stdio(iomod, fd, 1, "", encoding, errors); + if (std == NULL) goto error; - } } /* if (fd < 0) */ PySys_SetObject("__stdout__", std); PySys_SetObject("stdout", std); @@ -835,10 +903,9 @@ initstdio(void) #endif } else { - if (!(std = PyFile_FromFd(fd, "", "w", -1, encoding, - "backslashreplace", "\n", 0))) { + std = create_stdio(iomod, fd, 1, "", encoding, "backslashreplace"); + if (std == NULL) goto error; - } } /* if (fd < 0) */ /* Same as hack above, pre-import stderr's codec to avoid recursion -- cgit v0.12