Skip to content

Commit 93cef31

Browse files
author
antoine.pitrou
committed
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. git-svn-id: http://svn.python.org/projects/python/branches/py3k@68451 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent e57ae77 commit 93cef31

File tree

5 files changed

+102
-13
lines changed

5 files changed

+102
-13
lines changed

Include/pydebug.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ PyAPI_DATA(int) Py_IgnoreEnvironmentFlag;
1818
PyAPI_DATA(int) Py_DivisionWarningFlag;
1919
PyAPI_DATA(int) Py_DontWriteBytecodeFlag;
2020
PyAPI_DATA(int) Py_NoUserSiteDirectory;
21+
PyAPI_DATA(int) Py_UnbufferedStdioFlag;
2122

2223
/* this is a wrapper around getenv() that pays attention to
2324
Py_IgnoreEnvironmentFlag. It should be used for getting variables like

Lib/test/test_cmd_line.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ def test_run_code(self):
142142
self.exit_code('-c', command),
143143
0)
144144

145+
def test_unbuffered_output(self):
146+
# Test expected operation of the '-u' switch
147+
for stream in ('stdout', 'stderr'):
148+
# Binary is unbuffered
149+
code = ("import os, sys; sys.%s.buffer.write(b'x'); os._exit(0)"
150+
% stream)
151+
data, rc = self.start_python_and_exit_code('-u', '-c', code)
152+
self.assertEqual(rc, 0)
153+
self.assertEqual(data, b'x', "binary %s not unbuffered" % stream)
154+
# Text is line-buffered
155+
code = ("import os, sys; sys.%s.write('x\\n'); os._exit(0)"
156+
% stream)
157+
data, rc = self.start_python_and_exit_code('-u', '-c', code)
158+
self.assertEqual(rc, 0)
159+
self.assertEqual(data.strip(), b'x',
160+
"text %s not line-buffered" % stream)
161+
145162

146163
def test_main():
147164
test.support.run_unittest(CmdLineTest)

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 3.1 alpha 0
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line
16+
flag to work properly. Furthermore, when specifying -u, the text stdout
17+
and stderr streams have line-by-line buffering enabled (the default being
18+
to buffer arbitrary chunks of data).
19+
1520
- The internal table, _PyLong_DigitValue, is now an array of unsigned chars
1621
instead of ints (reducing its size from 4 to 8 times thereby reducing
1722
Python's overall memory).

Modules/main.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ Py_Main(int argc, wchar_t **argv)
292292
wchar_t *module = NULL;
293293
FILE *fp = stdin;
294294
char *p;
295-
int unbuffered = 0;
296295
int skipfirstline = 0;
297296
int stdin_is_interactive = 0;
298297
int help = 0;
@@ -374,7 +373,7 @@ Py_Main(int argc, wchar_t **argv)
374373
break;
375374

376375
case 'u':
377-
unbuffered++;
376+
Py_UnbufferedStdioFlag = 1;
378377
saw_unbuffered_flag = 1;
379378
break;
380379

@@ -423,7 +422,7 @@ Py_Main(int argc, wchar_t **argv)
423422
Py_InspectFlag = 1;
424423
if (!saw_unbuffered_flag &&
425424
(p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
426-
unbuffered = 1;
425+
Py_UnbufferedStdioFlag = 1;
427426

428427
if (!Py_NoUserSiteDirectory &&
429428
(p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0')
@@ -444,7 +443,7 @@ Py_Main(int argc, wchar_t **argv)
444443

445444
stdin_is_interactive = Py_FdIsInteractive(stdin, (char *)0);
446445

447-
if (unbuffered) {
446+
if (Py_UnbufferedStdioFlag) {
448447
#if defined(MS_WINDOWS) || defined(__CYGWIN__)
449448
_setmode(fileno(stdin), O_BINARY);
450449
_setmode(fileno(stdout), O_BINARY);

Python/pythonrun.c

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ int Py_UseClassExceptionsFlag = 1; /* Needed by bltinmodule.c: deprecated */
8888
int Py_FrozenFlag; /* Needed by getpath.c */
8989
int Py_IgnoreEnvironmentFlag; /* e.g. PYTHONPATH, PYTHONHOME */
9090
int Py_NoUserSiteDirectory = 0; /* for -s and site.py */
91+
int Py_UnbufferedStdioFlag = 0; /* Unbuffered binary std{in,out,err} */
9192

9293
/* PyModule_GetWarningsModule is no longer necessary as of 2.6
9394
since _warnings is builtin. This API should not be used. */
@@ -728,6 +729,75 @@ initsite(void)
728729
}
729730
}
730731

732+
static PyObject*
733+
create_stdio(PyObject* io,
734+
int fd, int write_mode, char* name,
735+
char* encoding, char* errors)
736+
{
737+
PyObject *buf = NULL, *stream = NULL, *text = NULL, *raw = NULL;
738+
const char* mode;
739+
const PyObject *line_buffering;
740+
int buffering;
741+
742+
if (Py_UnbufferedStdioFlag)
743+
buffering = 0;
744+
else
745+
buffering = -1;
746+
if (write_mode)
747+
mode = "wb";
748+
else
749+
mode = "rb";
750+
buf = PyObject_CallMethod(io, "open", "isiOOOi",
751+
fd, mode, buffering,
752+
Py_None, Py_None, Py_None, 0);
753+
if (buf == NULL)
754+
goto error;
755+
756+
if (!Py_UnbufferedStdioFlag) {
757+
raw = PyObject_GetAttrString(buf, "raw");
758+
if (raw == NULL)
759+
goto error;
760+
}
761+
else {
762+
raw = buf;
763+
Py_INCREF(raw);
764+
}
765+
766+
text = PyUnicode_FromString(name);
767+
if (text == NULL || PyObject_SetAttrString(raw, "_name", text) < 0)
768+
goto error;
769+
Py_CLEAR(raw);
770+
Py_CLEAR(text);
771+
772+
if (Py_UnbufferedStdioFlag)
773+
line_buffering = Py_True;
774+
else
775+
line_buffering = Py_False;
776+
stream = PyObject_CallMethod(io, "TextIOWrapper", "OsssO",
777+
buf, encoding, errors,
778+
"\n", line_buffering);
779+
Py_CLEAR(buf);
780+
if (stream == NULL)
781+
goto error;
782+
783+
if (write_mode)
784+
mode = "w";
785+
else
786+
mode = "r";
787+
text = PyUnicode_FromString(mode);
788+
if (!text || PyObject_SetAttrString(stream, "mode", text) < 0)
789+
goto error;
790+
Py_CLEAR(text);
791+
return stream;
792+
793+
error:
794+
Py_XDECREF(buf);
795+
Py_XDECREF(stream);
796+
Py_XDECREF(text);
797+
Py_XDECREF(raw);
798+
return NULL;
799+
}
800+
731801
/* Initialize sys.stdin, stdout, stderr and builtins.open */
732802
static int
733803
initstdio(void)
@@ -794,10 +864,9 @@ initstdio(void)
794864
#endif
795865
}
796866
else {
797-
if (!(std = PyFile_FromFd(fd, "<stdin>", "r", -1, encoding,
798-
errors, "\n", 0))) {
867+
std = create_stdio(iomod, fd, 0, "<stdin>", encoding, errors);
868+
if (std == NULL)
799869
goto error;
800-
}
801870
} /* if (fd < 0) */
802871
PySys_SetObject("__stdin__", std);
803872
PySys_SetObject("stdin", std);
@@ -814,10 +883,9 @@ initstdio(void)
814883
#endif
815884
}
816885
else {
817-
if (!(std = PyFile_FromFd(fd, "<stdout>", "w", -1, encoding,
818-
errors, "\n", 0))) {
886+
std = create_stdio(iomod, fd, 1, "<stdout>", encoding, errors);
887+
if (std == NULL)
819888
goto error;
820-
}
821889
} /* if (fd < 0) */
822890
PySys_SetObject("__stdout__", std);
823891
PySys_SetObject("stdout", std);
@@ -835,10 +903,9 @@ initstdio(void)
835903
#endif
836904
}
837905
else {
838-
if (!(std = PyFile_FromFd(fd, "<stderr>", "w", -1, encoding,
839-
"backslashreplace", "\n", 0))) {
906+
std = create_stdio(iomod, fd, 1, "<stderr>", encoding, "backslashreplace");
907+
if (std == NULL)
840908
goto error;
841-
}
842909
} /* if (fd < 0) */
843910

844911
/* Same as hack above, pre-import stderr's codec to avoid recursion

0 commit comments

Comments
 (0)