homepage

This issue tracker has been migrated to GitHub , and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: PyRun_InteractiveLoop fails to run interactively when using a Linux pty that's not tied to stdin/stdout
Type: behavior Stage: patch review
Components: Interpreter Core, Library (Lib) Versions: Python 3.9, Python 3.8, Python 3.7, Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Kevin.Barry, Yauheni Kaliuta, emmanuel, miss-islington, pmpp
Priority: normal Keywords: patch

Created on 2012年05月25日 15:42 by Kevin.Barry, last changed 2022年04月11日 14:57 by admin.

Files
File name Uploaded Description Edit
working.c Kevin.Barry, 2012年05月25日 15:42 A test program that hopefully causes the behavior described. You need xterm, and libreadline and libncurses (with the respective headers.)
Python-2.6.8-Run_Interactive-fix.patch Kevin.Barry, 2012年07月24日 01:26
working2.c Kevin.Barry, 2012年07月24日 01:39 A test program to be used with a version of Python built with the patch (Python-2.6.8-Run_Interactive-fix.patch) above.
Python-2.6.6-Run_Interactive-fix.patch Kevin.Barry, 2012年07月24日 15:46 The second iteration of a patch to fix interactivity from the C API.
working3.c Kevin.Barry, 2012年07月24日 15:54 A simplified version of the previous example that demonstrates the problem (before patch) and proper functionality (after patch.)
bug.sh emmanuel, 2013年03月17日 20:44
Pull Requests
URL Status Linked Edit
PR 22190 closed pmpp, 2020年09月10日 16:47
PR 31006 merged pmpp, 2022年01月29日 07:40
PR 31065 merged miss-islington, 2022年02月01日 22:34
Messages (19)
msg161586 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年05月25日 15:42
I have been trying to get PyRun_InteractiveLoop to run on a pty (Linux) without replacing stdin and stdout with that pty; however, it seems like Python (2.6.6) is hard-coded to only run interactively on stdin and stdout.
Compile the attached program with:
> gcc `python-config --cflags` working.c -o working `python-config --ldflags`
and run it with:
> ./working xterm -S/0
and you should see that there is no interactivity in the xterm that's opened.
Compile the attached file with:
> gcc -DREADLINE_HACK `python-config --cflags` working.c -o working `python-config --ldflags` -lreadline -lcurses
and run it with:
> ./working xterm -S/0
to see how it runs with my best attempt to get it to function properly with a readline hack. Additionally, try running:
> ./working xterm -S/0 > /dev/null
> ./working xterm -S/0 < /dev/null
both of which should cause interactivity in the xterm to fail, indicating that Python is checking stdin/stdout for tty status when determining if it should run interactively (i.e. it's not checking the tty status of the file passed to PyRun_InteractiveLoop.)
Am I somehow using this function wrong? I've been trying to work around this problem for a while, and I don't think I should be using readline hacks (especially since they don't port to other OSes with ptys, e.g. OS X.) I even tried to patch the call to PyOS_Readline in tok_nextc (Parser/tokenizer.c) to use tok->fp instead of stdin/stdout, which caused I/O to use the pty but it still failed to make interactivity work.
Thanks!
Kevin Barry
msg166258 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年07月24日 01:26
Here is a patch that corrects the problem (quoted below and attached.) This only corrects the problem when 'PyOS_ReadlineFunctionPointer' is set, e.g. you must 'import readline', otherwise Python will defer to stdin/stdout with 'PyOS_StdioReadline'.
The patch:
--- Python-2.6.8/Parser/tokenizer.c 2012年04月10日 11:32:11.000000000 -0400
+++ Python-2.6.8-patched/Parser/tokenizer.c 2012年07月23日 19:56:39.645992101 -0400
@@ -805,7 +805,7 @@
 return Py_CHARMASK(*tok->cur++);
 }
 if (tok->prompt != NULL) {
- char *newtok = PyOS_Readline(stdin, stdout, tok->prompt);
+ char *newtok = PyOS_Readline(tok->fp? tok->fp : stdin, tok->fp? tok->fp : stdout, tok->prompt);
 if (tok->nextprompt != NULL)
 tok->prompt = tok->nextprompt;
 if (newtok == NULL)
Kevin Barry
msg166259 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年07月24日 01:39
I've attached a new example source file to demonstrate the fix.
Compile the attached program with (*after* patching and installing Python):
> gcc `python-config --cflags` working2.c -o working2 `python-config --ldflags`
and run it with:
> ./working2 xterm -S/0 < /dev/null > /dev/null
(The redirection shows that it works when stdin/stdout aren't a tty.)
I looked at the most-recent revision of tokenizer.c (http://hg.python.org/cpython/file/52032b13243e/Parser/tokenizer.c) and see that the change in my patch above hasn't been made already.
Kevin Barry
msg166301 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年07月24日 15:46
The patch from before needed a slight modification for when Python actually defaults to an interactive session on stdin. Since I rebuild this for my current distro (Slackware64 13.37,) I switched to the Python 2.6.6 source. This might not be the proper way to handle the default case (e.g. 'Py_Main'), but it's a start.
The patch (also attached):
--- ./Parser/tokenizer.c.orig 2012年07月23日 22:24:56.513992301 -0400
+++ ./Parser/tokenizer.c 2012年07月23日 22:23:24.329992167 -0400
@@ -805,7 +805,7 @@
 return Py_CHARMASK(*tok->cur++);
 }
 if (tok->prompt != NULL) {
- char *newtok = PyOS_Readline(stdin, stdout, tok->prompt);
+ char *newtok = PyOS_Readline(tok->fp? tok->fp : stdin, (tok->fp && tok->fp != stdin)? tok->fp : stdout, tok->prompt);
 if (tok->nextprompt != NULL)
 tok->prompt = tok->nextprompt;
 if (newtok == NULL)
Kevin Barry
msg166303 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年07月24日 15:54
I've attached a simplified example program (working3.c) that demonstrates both the original problem and that the patch (Python-2.6.6-Run_Interactive-fix.patch) works. It eliminates the need for a pty, 'xterm', and redirection.
Compile the attached program with:
> gcc `python-config --cflags` working3.c -o working3 `python-config --ldflags`
and run it with (before and after patching):
> ./working3
Also, for verification, run 'python' with no arguments to show that default interactivity is preserved.
Kevin Barry
msg172411 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2012年10月08日 20:33
I still see the potential cause addressed by my patch in the 2.7, 3.3, and "default" branches, so I'm assuming that all versions from 2.6 on have this problem.
I also see that I can elect to change the "Status" and "Resolution" of this report. Does that mean I need to do something besides wait for someone involved in the project to look at my patch?
Kevin Barry
msg184395 - (view) Author: (emmanuel) Date: 2013年03月17日 20:16
run the attached shell script to observe the bug
./bug.sh 0 -> shows the bug
./bug.sh 1 -> shows the expected behaviour (using a workaround)
tested on linux with python 2.7
msg184399 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2013年03月17日 21:13
emmanuel,
Thanks for the suggestion. Your workaround is exactly the same as using dup2 (in C) to replace stdin/stdout/stderr with the pty, however. If you added the following lines to your C code, it would have the same effect as the command-line redirection in the workaround:
dup2(fileno(file), STDIN_FILENO);
dup2(fileno(file), STDOUT_FILENO);
dup2(fileno(file), STDERR_FILENO);
In fact, that's exactly what bash does after forking, just before executing "exe". In most cases, developers who use PyRun_InteractiveLoop in a pty probably also do exactly that, which is why I'm the only one who's reported this as a bug. For applications like mine, however, where the interactive Python session needs to be an unobtrusive add-on to an otherwise-complete program, this solution won't work. The standard file descriptors aren't disposable in most of the programs I work on.
Thanks again!
Kevin Barry
msg184403 - (view) Author: (emmanuel) Date: 2013年03月17日 22:47
Kevin,
Indeed the code I submitted can be written entirely in C using pipe fork execl dup2 etc. as you suggest. The only purpose of mixing bash and C is to have a short self-contained file showing the problem.
Anyway, whether in C or bash the workaround is less than satisfying in that it uses up fds 0 1 2, and I have exactly the same goal and constraints as you.
Finally, thanks for identifying the limitation in the python implementation and submitting a patch. Like you I hope it will be eventually applied.
msg184410 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2013年03月18日 00:52
One additional issue, which my patch doesn't address, is that PyRun_InteractiveLoop should really take *two* FILE* arguments, with the second one being optional. This is because on Linux (and presumably on other *nixes) if a read operation is blocked on a file descriptor then write operations (from other threads) to the same file descriptor will also block. That doesn't happen in the current Python implementations because PyOS_Readline is always called with two FILE* objects, anyway (stdin and stdout.) I would, however, expect such a problem to appear if a user created a Python thread in the interactive session that periodically printed to the terminal, then read input from the terminal. In that case, I would expect to see no output from the thread while the read operations were blocked, but I haven't tested it. (I don't remember if this came up after I applied my patch locally.)
I actually considered this when I created the patch; however, I didn't feel like going to all the trouble of adding a member to tok and propagating the change throughout the entire core. I had hoped this bug would get more attention and I'd be able to discuss it with a developer involved in the Python project, but ultimately that didn't happen and I ended up forgetting about it.
Kevin Barry
msg184537 - (view) Author: (emmanuel) Date: 2013年03月18日 22:11
Kevin,
These are good points.
I had a cursory look at the python source code and observed the following:
- There may also be a concern with stderr (used to print the prompt in PyOS_Readline)
- PyOS_Readline has two different definitions in files pgenmain.c and myreadline.c
- There is this interesting comment in myreadline.c:
/* By initializing this function pointer, systems embedding Python can
 override the readline function.
 Note: Python expects in return a buffer allocated with PyMem_Malloc. */
char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, char *);
This pointer is actually used (set it to (void*)1 and the interpreter crashes) so it could offer a means to redirect stdin as we want. For stdout/stderr further investigation is needed.
msg184812 - (view) Author: (emmanuel) Date: 2013年03月20日 22:58
Kevin,
I've read more carefully your messages and investigated some more.
It seems that there are several issues:
1/ To take input from a defined tty without interfering with standard file descriptors
2/ To have the result (object) of evaluation printed to a defined tty without interfering with standard file descriptors
3/ (optionally) To direct to the tty (or not) the output that is a side effect of the evaluation
Provided that no one messes with PyOS_ReadlineFunctionPointer (as with "import readline") it should be possible to solve 1 without modifying Python, and "approximately" solve 2 (with 3 implied out of necessity).
On the other hand, modifying Python as you suggest could solve 1, but issues 2 and 3 would still remain and probably require some other modifications.
msg185144 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2013年03月24日 16:47
emmanuel,
Regarding your points: All three can be taken care of with a combination of my patch and setting sys.stdin, sys.stdout, and sys.stderr to the pty. (That should really be done internally with another patch, since os.fdopen is OS-specific. Also, sys.stdin, sys.stdout, and sys.stderr should each have distinct underlying file descriptors, which I didn't do in working.c.) Those can safely be replaced since they're just the "effective" standard files, and sys.__stdin__ et al. refer to the actual C stdin et al. The remaining issue would be that the same descriptor shouldn't be used for both input and output in the interpreter loop, especially if the FILE* passed is only open for reading (since standard input technically doesn't have to be writable.)
Kevin Barry
msg185229 - (view) Author: (emmanuel) Date: 2013年03月25日 20:21
Kevin,
I now fully agree with you. Regarding points 2 & 3 I dismissed modifying sys.stdin/out in python out of hand because it still would not allow to have a proper behaviour with two concurrent consoles on the same interpreter. Anyway this is not a very meaningful use-case, so modifying sys.stdin/out looks like the best solution.
As for point 1, it can be worked around in a twisted and probably non-portable way (setting PyOS_ReadlineFunctionPointer to a custom function that forks a child which runs GNU readline or whatnot on its fds 0,1,2, and which communicates with the parent through pipes) but it's a pity that from PyOS_Readline() on, argument sys_stdin is correctly passed down, and that the chain has only this gap in tok_nextc() which dismisses the caller's argument and uses plain stdin.
If the user arguments are not used, why does PyRun_InteractiveLoop() take any arguments at all?
It would be nice to know the opinion of the python development team and to work out a complete fix (considering also stdout as you suggest) under their authority.
msg185250 - (view) Author: Kevin Barry (Kevin.Barry) Date: 2013年03月25日 23:59
emmanuel,
The Python interpreter isn't reentrant, so you could only run two interactive sessions connected to the same Python environment if you implemented your own REPL function that unlocked the GIL when waiting for input, then lock it just long enough to interpret the input.
Also, the readline module already does what you suggest (sets PyOS_ReadlineFunctionPointer to a GNU libreadline wrapper.) My "readline hack" (working.c) forces it to behave as it's supposed to. Rather, it *undoes* what PyRun_InteractiveLoop does every iteration, which is pass stdin/stdout for I/O, which libreadline in turn uses.
I agree that it would be nice to get a Python developer involved, mostly because I expect things to break when this problem is fixed. Bad things happen when you think you've tested a lot of different cases that actually turn out to be the exact same case.
Kevin Barry
msg252224 - (view) Author: Yauheni Kaliuta (Yauheni Kaliuta) Date: 2015年10月03日 18:44
Any progress with the problem? I just wanted to use the feature, but it looks like the bug.sh is still reproduces the bug.
msg376640 - (view) Author: pmp-p (pmpp) * Date: 2020年09月09日 14:21
all PyRun_InteractiveOne* functions are also affected
it is really annoying when implementing repl behaviour when embedding( use cases : pyodide, android, wasi )
But I think the correct patch is : 
- char *newtok = PyOS_Readline(stdin, stdout, tok->prompt);
+ char *newtok = PyOS_Readline(tok->fp? tok->fp : stdin, stdout, tok->prompt);
msg412317 - (view) Author: miss-islington (miss-islington) Date: 2022年02月01日 22:34
New changeset 89b13042fcfc95bae21a49806a205ef62f1cdd73 by Paul m. p. P in branch 'main':
bpo-14916: use specified tokenizer fd for file input (GH-31006)
https://github.com/python/cpython/commit/89b13042fcfc95bae21a49806a205ef62f1cdd73
msg412486 - (view) Author: miss-islington (miss-islington) Date: 2022年02月03日 23:32
New changeset 91e888904478271c27c52c773863b41f5a8f7f30 by Miss Islington (bot) in branch '3.10':
bpo-14916: use specified tokenizer fd for file input (GH-31006)
https://github.com/python/cpython/commit/91e888904478271c27c52c773863b41f5a8f7f30
History
Date User Action Args
2022年04月11日 14:57:30adminsetgithub: 59121
2022年02月03日 23:32:27miss-islingtonsetmessages: + msg412486
2022年02月01日 22:34:17miss-islingtonsetpull_requests: + pull_request29250
2022年02月01日 22:34:02miss-islingtonsetnosy: + miss-islington
messages: + msg412317
2022年01月29日 07:40:57pmppsetpull_requests: + pull_request29187
2020年09月10日 16:47:27pmppsetstage: patch review
pull_requests: + pull_request21251
2020年09月10日 16:32:53pmppsetversions: + Python 3.6, Python 3.7, Python 3.8, Python 3.9, - Python 2.6, Python 3.1, Python 2.7, Python 3.2, Python 3.3, Python 3.4, Python 3.5
2020年09月09日 14:44:21vstinnersetnosy: - vstinner
2020年09月09日 14:21:03pmppsetnosy: + pmpp
messages: + msg376640
2015年10月03日 18:44:55Yauheni Kaliutasetnosy: + Yauheni Kaliuta
messages: + msg252224
2013年03月25日 23:59:03Kevin.Barrysetmessages: + msg185250
2013年03月25日 20:21:41emmanuelsetmessages: + msg185229
2013年03月24日 16:47:35Kevin.Barrysetmessages: + msg185144
2013年03月20日 22:58:33emmanuelsetmessages: + msg184812
2013年03月18日 22:11:18emmanuelsetmessages: + msg184537
2013年03月18日 00:52:37Kevin.Barrysetmessages: + msg184410
2013年03月17日 22:47:48emmanuelsetmessages: + msg184403
2013年03月17日 21:13:36Kevin.Barrysetmessages: + msg184399
2013年03月17日 20:44:03emmanuelsetfiles: + bug.sh
2013年03月17日 20:43:34emmanuelsetfiles: - bug.sh
2013年03月17日 20:16:54emmanuelsetfiles: + bug.sh

messages: + msg184395
2013年03月17日 14:28:10emmanuelsetnosy: + emmanuel
2012年10月08日 21:13:48pitrousetnosy: + vstinner
2012年10月08日 20:33:48Kevin.Barrysetmessages: + msg172411
versions: + Python 3.1, Python 2.7, Python 3.2, Python 3.3, Python 3.4, Python 3.5
2012年07月24日 15:54:23Kevin.Barrysetfiles: + working3.c

messages: + msg166303
2012年07月24日 15:46:27Kevin.Barrysetfiles: + Python-2.6.6-Run_Interactive-fix.patch

messages: + msg166301
2012年07月24日 01:39:09Kevin.Barrysetfiles: + working2.c
status: pending -> open
messages: + msg166259
2012年07月24日 01:26:30Kevin.Barrysetstatus: open -> pending
files: + Python-2.6.8-Run_Interactive-fix.patch
messages: + msg166258

keywords: + patch
2012年05月25日 15:42:37Kevin.Barrycreate

AltStyle によって変換されたページ (->オリジナル) /