Skip to content
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

Cannot call setupterm() more than once per process #33

Closed
jquast opened this issue Aug 30, 2013 · 6 comments
Closed

Cannot call setupterm() more than once per process #33

jquast opened this issue Aug 30, 2013 · 6 comments

Comments

@jquast
Copy link
Collaborator

jquast commented Aug 30, 2013

Whichever terminal "kind" was used when Terminal() was first instantiated will continue to be used for the remainder of the program, regardless of the value of "kind" argument to subsequent instantiations.

This is because of the curses C api that python hooks through, and some things done in setupterm() that last for the duration of the process.

The correct fix, would be that every time Terminal() is instantiated, a subprocess is spanwed, and termcap translation later requested is piped through it. This is the method that I use, by way that the first Terminal() instantiation occurs in sub-process, and not a thread. This may however be too bloated.

An intermediate fix that I would be happy to patch propose, would be to generate a warning -- if Terminal() is instantiated again, without the same "kind", then a warning is emitted.

$ python3 -c 'import blessings;print(repr(blessings.Terminal(kind="xterm").red_on_black));print(repr(blessings.Terminal(kind="vt220").red_on_black));'
'\x1b[31m\x1b[40m'
'\x1b[31m\x1b[40m'

$ python3 -c 'import blessings;print(repr(blessings.Terminal(kind="vt220").red_on_black));print(repr(blessings.Terminal(kind="xterm").red_on_black));'
''
''

@erikrose
Copy link
Owner

The warning would be a good, inexpensive start. (Exception? warnings module? Redesign the API so kind is a singleton? All of these have disadvantages.) People are always free to take the simple Terminal we give them and spin it off in a multiprocess environment like you did, so you're right: there's no need to complicate unless we find this is a common gotcha.

But let's back up a sec. What's setupterm doing under the covers? It looks to me, from reading man pages, that it's setting up a bunch of per-process state by reading the terminfo DB into the process space (speaking very roughly). If it was simply blowing codes through a file descriptor, we could switch that from stdout and be on our mrery way, but I don't think that's all there is to it.

I haven't been able to find a way to interact with terminfo from Python without going through setupterm first. Is it any easier from other languages? At least, researching this, I came to understand the problem of our vt102 messiness: #9 (comment).

@erikrose
Copy link
Owner

Is this what you were looking at? http://bugs.python.org/issue7567

@jquast
Copy link
Collaborator Author

jquast commented Sep 1, 2013

bug 7567 isn't specifically about this issue (cannot call initscr/endwin more than once w/o corruption), but the final comment by ferringb is:

As it is now, they wind up locked into whatever term they first invoked it with, getting back term codes from tigetstr for the previous term..." is exactly what we're describing here.

@jquast
Copy link
Collaborator Author

jquast commented Sep 1, 2013

"What's setupterm doing under the covers?"

There is a global variable, "cur_term" in C-land, pointing to a TERMINAL structure of capabilities. This gets initialized by setupterm:
/

  •  Find and read the appropriate object file for the terminal
    
  •  Make cur_term point to the structure.
    
    */

this is a global value you'll find all over libcurses, via term.h:

./base/lib_newterm.c:#include <term.h> /* clear_screen, cup & friends, cur_term /
./base/lib_restart.c:#include <term.h> /
lines, columns, cur_term /
./base/lib_set_term.c:#include <term.h> /
cur_term /
./tinfo/lib_baudrate.c:#include <term.h> /
cur_term, pad_char /
./tinfo/lib_kernel.c:#include <term.h> /
cur_term /
./tinfo/lib_raw.c:#include <term.h> /
cur_term /
./tinfo/lib_setup.c:#include <term.h> /
lines, columns, cur_term /
./tinfo/lib_ttyflags.c:#include <term.h> /
cur_term /
./tinfo/read_bsd_terminfo.c:#include <term.h> /
lines, columns, cur_term /
./trace/lib_tracebits.c:#include <term.h> /
cur_term */

To properly use setupterm() for more than one definition, it is necessary to save and restore the value pointed to by (TERMINAL *) cur_term. Unfortunately, you won't find any mention, and therefor any visibility to the value of cur_term in Python's curses module (./Modules/_cursesmodule.c), so it is simply not possible to access this definition in any way, much less save/restore it.

Refs:

curses C code for setupterm -- ignore the if (reuse && ..) code, as reuse is explicitly set FALSE, at least in OBSD's impl.: http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libcurses/tinfo/lib_setup.c?rev=1.12

IBM AIX's manual page explicitly mentions the need to save/restore cur_term: http://pic.dhe.ibm.com/infocenter/aix/v6r1/index.jsp?topic=%2Fcom.ibm.aix.basetechref%2Fdoc%2Fbasetrf2%2Fsetupterm.htm

@jquast
Copy link
Collaborator Author

jquast commented Sep 8, 2013

for the test cases, it appears that correct test cases for different kinds of Terminals/calls to setupterm() can be achieved by using different test.py files for each, as each test file is run in a seperate process.

Working on this now. Finding test cases that probably should not have succeeded, or have been evaluated in the way that they were, but were, simply because of this issue!

@jquast
Copy link
Collaborator Author

jquast commented Nov 5, 2013

closed by pull request #50

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants