PEP 433: Choose the default value of the new cloexec parameter

Hi, The PEP 433 proposes different options to add a new cloexec parameter: a) cloexec=False by default b) cloexec=True by default c) configurable default value I tried to list in the PEP 433 advantages and drawbacks of each option. If I recorded correctly opinions, the different options have the following supporters: a) cloexec=False by default b) cloexec=True by default: Charles-François Natali c) configurable default value: Antoine Pitrou, Nick Coghlan, Guido van Rossum I don't know if it's enough to say that (c) is the chosen option. -- For the name of the new parameter, I still prefer "cloexec" for the reason given by Antoine: on UNIX, the flag has only an effect on exec(), not on fork(). So inherited=False can be misleading on UNIX. I updated the rationale of the PEP to describe the close-on-exec flag: http://www.python.org/dev/peps/pep-0433/#rationale -- FYI on Windows, cloexec=False (inherited=True) doesn't mean that the file descriptor will be inherited. It also depends on the bInheritHandles parameter of the CreateProcess() function. Extract of the PEP: "On Windows, the file descriptor is not inherited if the close-on-exec flag is set, the file descriptor is inherited by child processes if the flag is cleared and if CreateProcess() is called with the bInheritHandles parameter set to TRUE (when subprocess.Popen is created with close_fds=False for example)." Victor

Hello,
You can actually count me in the cloexec=False camp, and against the idea of a configurable default value. Here's why: Why cloexec shouldn't be set by default: - While it's really tempting to fix one of Unix historical worst decisions, I don't think we can set file descriptors cloexec by default: this would break some applications (I don't think there would be too many of them, but still), but most notably, this would break POSIX semantics. If Python didn't expose POSIX syscalls and file descriptors, but only high-level file streams/sockets/etc, then we could probably go ahead, but now it's too late. Someone said earlier on python-dev that many people use Python for prototyping, and indeed, when using POSIX API, you expect POSIX semantics. Why the default value shouldn't be tunable: - I think it's useless: if the default cloexec behavior can be altered (either by a command-line flag, an environment variable or a sys module function), then libraries cannot rely on it and have to make file descriptors cloexec on an individual basis, since the default flag can be disabled. So it would basically be useless for the Python standard library, and any third-party library. So the only use case is for application writers that use raw exec() (since subprocess already closes file descriptors > 3, and AFAICT we don't expose a way to create processes "manually" on Windows), but there I think they fall into two categories: those who are aware of the problem of file descriptor inheritance, and who therefore set their FDs cloexec manually, and those who are not familiar with this issue, and who'll never look up a sys.setdefaultcloexec() tunable (and if they do, they might think: "Hey, if that's so nice, why isn't it on by default? Wait, it might break applications? I'll just leave the default then."). - But most importantly, I think such a tunable flag is a really wrong idea because it's a global tunable that alters the underlying operating system semantics. Consider this code: """ r, w = os.pipe() if os.fork() == 0: os.execve(['myprog']) """ With a tunable flag, just by looking at this code, you have no way to know whether the file descriptor will be inherited by the child process. That would be introducing an hidden global variable silently changing the semantics of the underlying operating system, and that's just so wrong. Sure, we do have global tunables: """ sys.setcheckinterval() sys.setrecursionlimit() sys.setswitchinterval() hash_randomization """ But those alter "extralinguistic" behavior, i.e. they don't affect the semantics of the language or underlying operating system in a way that would break or change the behavior of a "conforming" program. Although it's not as bad, just to belabor the point, imagine we introduced a new method: """ sys.enable_integer_division(boolean) Depending on the value of this flag, the division of two integers will either yield a floating point or truncated integer value. """ Global variables are bad, hidden global variables are worse, and hidden global variables altering language/operating system semantics are evil :-) What I'd like to see: - Adding a "cloexec" parameter to file descriptor creating functions/classes is fine, it will make it easier for a library/application writer to create file descriptors cloexec, especially in an atomic way. - We should go over the standard library, and create FDs cloexec if they're not handed over to the caller, either because they're opened/closed before returning, or because the underlying file descriptor is kept private (not fileno() method, although it's relatively rare). That's the approach chosen by glibc, and it makes sense: if another thread forks() while a thread is in the middle of getpwnam(), you don't want to leak an open file descriptor to /etc/passwd (or /etc/shadow). cf

On Fri, Jan 25, 2013 at 6:56 PM, Charles-François Natali <cf.natali@gmail.com> wrote:
It's a configurable setting in the same way that -Q makes the behaviour of "/" configurable in Python 2 (so your hypothetical example isn't hypothetical at all - it's a description the -Q option), and -R makes random hashing configurable in 2.7 and 3.2: it means we can change the default behaviour in a future version (perhaps Python 4) while allowing people to easily check if their code operates correctly in that state in the current version. I think the default behaviour needs to be configurable from the environment and the command line, but I don't believe it should be configurable from within the interpreter. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

sys.setdefaultcloexec() is convinient for unit test, but it may also be used by modules. A web framework may want to enable close-on-exec flag by default. The drawback of changing the default value after Python started is that Python may have created file descriptors before, so you cannot guarantee that all existing file descriptors have the flag set. Well, I don't know if sys.setdefaultcloexec() is a good idea or not :-) Victor

Le Fri, 25 Jan 2013 12:28:10 +0100, Victor Stinner <victor.stinner@gmail.com> a écrit :
Both Charles-François and Nick have good points. sys.setdefaultcloexec() is still useful if you want to force the policy from a Python script's toplevel (it's more practical than trying to fit a command-line option in the shebang line). Regards Antoine.

On Fri, Jan 25, 2013 at 9:36 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
Right, I'm only -0 on that aspect. It strikes me as somewhat dubious, but it's not obviously wrong the way a runtime equivalent of -Q or -R would be. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, Jan 25, 2013 at 10:07 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I just realised I could be converted to a +0 if the runtime time switch could only be used to set the global default as "cloexec=True" and couldn't be used to switch it back again (for testing purposes, if you only want to switch it on temporarily, use a subprocess). That way, as soon as you saw "sys.setdefaultcloexec()" at the beginning of __main__, you'd know descriptors would only be inherited when cloexec=False was set explicitly. If the default flag can also be turned off globally (rather than being a one-way switch), then you have no idea what libraries might do behind your back. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

2013/1/25 Nick Coghlan <ncoghlan@gmail.com>:
Oh, I like this idea. It does simplify many things :-) (And I agree that subprocess can be used to run a test which requires cloexec to be True by default.) -- I tried to be future-proof. If we decide to enable close-on-exec flag globally by default, how do you disable the flag globally? We may add an inverse command line option and another environment variable (ex: PYTHONNOCLOEXEC), but what about sys.setdefaultcloexec()? In a previous version, my implementation expected an argument for PYTHONCLOEXEC: PYTHONCLOEXEC=0 or PYTHONCLOEXEC=1. I realized that it's not how other options (like PYTHONDONTWRITEBYTECODE) are designed. But do we really want to enable close-on-exec in the future? Charles François has really good arguments against such choice :-) It's maybe better to consider that the default at startup will always be False. So we should only provide different ways to set the default to True. Victor

On Sat, Jan 26, 2013 at 12:54 AM, Victor Stinner <victor.stinner@gmail.com> wrote:
But do we really want to enable close-on-exec in the future? Charles François has really good arguments against such choice :-)
While many of Python's APIs are heavily inspired by POSIX, it still isn't POSIX. It isn't C either, especially in Python 3 (where C truncating division must be written as "//" and our internal text handling has made the migration to being fully Unicode based)
I think Charles François actually hit on a fairly good analogy by comparing this transition to the integer division one. To implement that: 1. In 2.x, the "-Q" option was introduced to allow the behaviour to be switched globally, while ensuring it remained consistent for the life of a given application 2. The "//" syntax was introduced to force the use of truncating integer division 3. "float(n) / d" could be used to force floating point division Significantly, code written using either option 2 or option 3 retains exactly the same semantics in Python 3, even though the default behaviour of "/" has now switched from truncating division to floating point division. Note also that in Python 3, there is *no* mechanism to request truncating division globally - if you want truncating division, you have to explicitly request it every time. So, if we agree that "cloexec-by-default" is a desirable goal, despite the inconsistency with POSIX (just as changing integer division was seen as desirable, despite the inconsistency with C), then a sensible transition plan becomes: 1. Introduce a mechanism to switch the behaviour globally, while ensuring it remains consistent for the life of a given application 2. Introduce the "cloexec=None/True/False" 3-valued parameter as needed to allow people to choose between default/definitely-cloexec/definitely-not-cloexec. 3. At some point in the future (perhaps in 3.5, perhaps in 4.0) switch the default behaviour to cloexec=True and remove the ability to change the behaviour globally. The reason I'd be +0 on a "one-way switch", even at runtime, is that you can just make it a no-op after that behaviour becomes the default. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

I don't plan to enable cloexec by default in a near future, nor in python 4. Someone else may change that later. Having a global flag should be enough. Enabling cloexec by default is interesting if *all* libraries respect the global flag (even C libraries), so subprocess can be used with close_fds=False. Closing all file descriptors is slow, especially on FreeBSD (it's now faster on linux, python lists /proc/pid/fd/). Victor

On 25Jan2013 21:07, Nick Coghlan <ncoghlan@gmail.com> wrote: | It's a configurable setting in the same way that -Q makes the | behaviour of "/" configurable in Python 2 (so your hypothetical | example isn't hypothetical at all - it's a description the -Q option), | and -R makes random hashing configurable in 2.7 and 3.2: it means we | can change the default behaviour in a future version (perhaps Python | 4) while allowing people to easily check if their code operates | correctly in that state in the current version. | | I think the default behaviour needs to be configurable from the | environment and the command line, but I don't believe it should be | configurable from within the interpreter. Hmm. This I can live with more happily, though I'm still uneasy. As an aside, I tend to feel that if something is tuneable it should be exposed within the interpreter. Maybe only in an exciting new module called shoot_self_in_foot or some similarly alarming name... Cheers, -- Cameron Simpson <cs@zip.com.au> That said, I'm inclined to agree that that's not necessarily a good idea. I always wanted to write a little program that would pop up a Mac window to ask ``I'm going to amputate a limb at random from you now.'' to see how many people would instinctively click "OK". - Marc VanHeyningen <mvanheyn@cs.indiana.edu>

On Sat, Jan 26, 2013 at 8:48 PM, Cameron Simpson <cs@zip.com.au> wrote:
I had missed this detail. I agree that it should be exposed in the interpreter. To my mind it is more like PYTHONPATH (which corresponds roughly to sys.path manipulations) than like -R (which changes something that should never be changed again, otherwise the sanity of the interpreter be at risk). It would seem hard to unittest the feature if it cannot be changed from within. But I can also think of other use cases for changing it from within (e.g. a script that decides on how to set it using a computation based on its arguments). -- --Guido van Rossum (python.org/~guido)

2013/1/27 Guido van Rossum <guido@python.org>:
sys.path is usually only used to add a new path, not to remove path from other libraries. I'm not sure that it's the best example to compare it to sys.setdefaultcloexec(). If sys.setdefaultcloexec() accepts an argument (so allow sys.setdefaultcloexec(False)), problems happen when two libraries, or an application and a library, disagree. Depending how/when the library is loaded, the flag may be True or False. I prefer to have a simple sys.setdefaultcloexec() which always set the flag to True. It's also simpler to explain how the default value is computed (it's less surprising). -- Unit tests can workaround the limitation using subprocesses. My implementation doesn't use sys.setdefaultcloexec() anymore, it just ensures that functions respect the current default value. I ran tests manually to test both default values (True and False). Tests may be improved later to test both defalut values using subprocess. Victor

On Sun, 27 Jan 2013 12:23:15 +0100 Victor Stinner <victor.stinner@gmail.com> wrote:
I don't think such limitations are very useful in practice. Users calling sys.setdefaultexec() will have to be sufficiently knowledgeable to understand the implications, anyway. Regards Antoine.

On Sun, Jan 27, 2013 at 9:29 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
I've yet to hear a use case for being able to turn it off globally if the application developer has indicated they want it on. A global flag that can be turned off programmatically is worse than no global flag at all. If we're never going to migrate to cloexec-on-by-default, then there simply shouldn't be a global flag - the option should just default to False. If we're going to migrate to cloexec-on-by-default some day, then the global flag should purely be a transition strategy to allow people to try out that future behaviour and adjust their application appropriately (by clearing the flag on descriptors that really need to be inherited). The typical default that should be assumed by library code will still be cloexec-off-by-default. A completely flexible global flag is just a plain bad idea for all the reasons Charles-François gave. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sun, Jan 27, 2013 at 3:45 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Out of context that last statement sounds pretty absurd. :-)
That doesn't seem to follow. We might never want to turn it on by default for reasons of compatibility with POSIX (whatever you say, POSIX semantics seep through Python in many places). But it might still be useful to be able to turn it on for specific programs (and doing that in code is a lot more convenient than having to say "run this app with python --cloexec", which sounds a real pain).
But typical library code won't care one way or another, will it?
A completely flexible global flag is just a plain bad idea for all the reasons Charles-François gave.
Honestly, what happened to the idea that we're all adults here? The likelihood that someone is clueless enough to misuse the flag and yet clueful enough to find out how to change it seems remote -- and they pretty much deserve what they get. It's like calling socket.settimeout(0.1) and then complaining that urllib.urlopen() raises exceptions, or calling sys.setrecursionlimit(1000000) when you are getting StackOverflowError. The only reason I can see why a flag should never be changeable after the fact is when it would end up violating the integrity of the interpreter. But I don't see that here. I just see paranoia about protecting users from all possible security issues. That seems a hopeless quest. That said, I'm sure I'll survive if you ignore my opinion, so I explicitly give you that option -- in the grand scheme of things I can't care that much about this and it seems a mostly theoretical issue. (And, an attacker who can exploit file descriptors given to it can probably find plenty of other attacks on a system they've broken into.) -- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
It's like calling socket.settimeout(0.1) and then complaining that urllib.urlopen() raises exceptions
but that's not what's happening. you'll see urllib.urlopen raising exceptions and only afterwards realize that you called into some third party library code that decided to change the timeout.

Yeah, so the answer to all this is that 3rd party libraries know better than to mess with global settings. On Mon, Jan 28, 2013 at 1:27 PM, Ralf Schmitt <ralf@systemexit.de> wrote:
-- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
Yeah, so the answer to all this is that 3rd party libraries know better than to mess with global settings.
Right. But why make it configurable at runtime then? If you're changing the value, then you're the one probably breaking third party code.

Sigh. This is getting exasperating. There's other code that might want to change this besides 3rd party library code. E.g. app configuration code. On Mon, Jan 28, 2013 at 1:56 PM, Ralf Schmitt <ralf@systemexit.de> wrote:
-- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
So, third party library code should know better, while at the same time it's fine to mess with global settings from app configuration code. That does not make sense.

On 01/28/2013 04:17 PM, Ralf Schmitt wrote:
Library code should not be relying on globals settings that can change. Library code should be explicit in its calls so that the current value of a global setting is irrelevant. ~Ethan~ P.S. Unless, of course, the library is meant to work with that global, and can work correctly with all of the global's valid settings.

That's one of the problems I've raised with this global flag since the beginning: it's useless for libraries, including the stdlib (and, as a reminder, this PEP started out of a a bug report against socket inheritance in socketserver). And once again, it's an hidden global variable, so you won't be able any more to tell what this code does: """ r, w = os.pipe() if os.fork() == 0: os.close(w) os.execve(['myprog']) """ Furthermore, if the above code is part of a library, and relies upon 'r' FD inheritance, it will break if the user sets the global cloexec flag. And the fact that a library relies upon FD inheritance is an implementation detail, the users shouldn't have to wonder whether enabling a global flag (in their code, not in a library) will break a given library: the only alternative for such code to continue working would be to pass cloexec=True explicitly to os.pipe()... The global socket.settimeout() is IMO a bad idea, and shouldn't be emulated. So I'm definitely -1 against any form of tunable value (be it a sys.setdefaultcloexec(), an environment variable or command-line flag), and still against changing the default value. But I promise that's the last time I'm bringing those arguments up, and I perfectly admit that some people want it as much as I don't want it :-) cf

On Tue, Jan 29, 2013 at 4:13 PM, Charles-François Natali <cf.natali@gmail.com> wrote:
I now think the conservative option is to initially implement the design in 3.4 without an option to change the global default. The "cloexec=True/False" model at least *supports* a tunable default, as well as eventually *changing* the default if we choose to do so, but I don't think there's any hurry to proceed to those steps. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Library code should not be relying on globals settings that can change.
Did you try my implementation of the PEP 433 on your project? Did you review my patch implementing the PEP 433? http://hg.python.org/features/pep-433 http://bugs.python.org/issue17036 I expected more change, whereas only very few modules of the stdlib need changes to support both modes (cloexec=False or cloexec=True by default). I made 5 different types of changes. Only th change (3) gives you an idea of how many modules must be fixed to support cloexec=True by default: 4 modules (http.server, multiprocessing, pty, subprocess) on a total of something like 200 modules (stdlib). And I'm not sure that all these modules need to be modified, I have to check again. By the way, I chose to not consider file descriptors 0, 1 and 2 as special: the developer must disable cloexec explicitly for standard streams. If we consider them as special, fewer (or no) modules would require changes. -- (1) add a cloexec parameter, modified modules: * io * asyncore * socket (2) explicitly enable cloexec: usually for security reasons, don't leak a file descriptor in a child process => it's not directly related to this PEP, I should maybe do this in a different commit. Said differently: code works with cloexec enabled or disabled, but I consider that enabled is safer. * cgi * getpass * importlib * logging * os * pty * pydoc * shutil (3) explicitly disable cloexec: code doesn't work if cloexec is set (code relies on file descriptor inherance); modified modules: * http.server: CGI uses dup2() to replace stdin and stdout * multiprocessing: stdin is replaced with /dev/null * pty: dup2() to replace stdin, stdout, stderr * subprocess: dup2() to replace stdin, stdout, stderr (4) refactoring to use the new cloexec parameter: * tempfile (5) apply the default value of the cloexec parameter (in C modules): * curses * mmap * oss * select * random Victor

On Tue, 29 Jan 2013 01:17:35 +0100 Ralf Schmitt <ralf@systemexit.de> wrote:
Yes, it's fine, because an application developer can often control (or at least study) the behaviour of all the code involved. It's the same story as with logging configuration and similar process-wide settings. Libraries shouldn't mess with it but the top-level application definitely can (and should, even). (and if you think many third-party libraries call fork()+exec() as part as their normal duty, then I've got a bridge to sell you) Regards Antoine.

Antoine Pitrou <solipsis@pitrou.net> writes:
Yes, it's fine, because an application developer can often control (or at least study) the behaviour of all the code involved.
I'd rather not have to check if some library messes with that global setting and work around it if it does! The fact that you can control and work around a global setting that may change, isn't a reason to introduce that global setting and the increased complexity that comes with it.
That's a bad comparison, because the situation is quite different with the logging module. Configuration of the logging module does not change the behaviour for code calling into the logging module (well, besides log output being produced somewhere, but that doesn't matter for the caller).

Antoine Pitrou <solipsis@pitrou.net> writes:
I was talking about some library changing the flag. Not changing the flag myself doesn't help in that case. I'll probably have to explicitly pass a cloexec argument to each fd creating function I'm calling in order to not be dependent on the global setting. Please just acknowledge that having a global configurable setting may lead to problems. Why is sys.setdefaultencoding being hidden in python 2?

Le Tue, 29 Jan 2013 11:24:49 +0100, Ralf Schmitt <ralf@systemexit.de> a écrit :
Please just acknowledge that having a global configurable setting may lead to problems.
Ralf, I won't "acknowledge" anything just so that it makes you feel better. Your point has been made and has also been rebutted. Let's agree to disagree and move along. Regards Antoine.

2013/1/25 Charles-François Natali <cf.natali@gmail.com>:
You can actually count me in the cloexec=False camp, and against the idea of a configurable default value.
Oh ok.
Agreed.
In my experience, in most cases, the default value of cloexec just doesn't matter at all. If your program relies on the state of the close-on-exec flag: you have to make it explicit, specify cloexec paramater. Example: os.pipe(cloexec=True). If you don't modify your application, it will just not work using -e command line option (or PYTHONCLOEXEC environment variable). But why would you enable cloexec by default if your application is not compatible?
The problem is that Python can be embeded in application: the application can start a child process independently of Python.
If this snippet doesn't work with cloexec enabled by default, you have to write: os.pipe(cloexec=False).
I started to make cloexec explicit *everywhere* in the Python stdlib. I reverted my commit because I think that only a few application start child processes, so doing extra work (additionnal syscalls to set cloexec) would slowdown Python for no gain. See my revert commit to see how many functions need to be modified: http://hg.python.org/features/pep-433/rev/963e450fc24f That's why I really like the idea of being able to configure the default value of the cloexec parameter. By default: no overhead nor backward compatibility issue. Whereas you can set close-on-exec flag *everywhere* if you are concern by all issues of inheriting file descriptors (cases listed in the PEP). In the stdlib, I only specified cloexec parameter where it was required to ensure that it works with any default value. Only a few modules were modified (subprocess, multiprocessing, ...). Victor

On 25Jan2013 12:24, Victor Stinner <victor.stinner@gmail.com> wrote: | 2013/1/25 Charles-François Natali <cf.natali@gmail.com>: | > You can actually count me in the cloexec=False camp, and against the | > idea of a configurable default value. | | Oh ok. I'm leaning to this view myself also. | > Why the default value shouldn't be tunable: | > - I think it's useless: if the default cloexec behavior can be altered | > (either by a command-line flag, an environment variable or a sys | > module function), then libraries cannot rely on it and have to make | > file descriptors cloexec on an individual basis, since the default | > flag can be disabled. | | In my experience, in most cases, the default value of cloexec just | doesn't matter at all. If your program relies on the state of the | close-on-exec flag: you have to make it explicit, specify cloexec | paramater. Example: os.pipe(cloexec=True). If you don't modify your | application, it will just not work using -e command line option (or | PYTHONCLOEXEC environment variable). But why would you enable cloexec | by default if your application is not compatible? Because we're not all writing applications. I write a lot of small library routines and then use them in my apps. On a UNIX system I can plow on presuming cloexec=False, and will feel no desire to label it in any calls. And so I might be making pipes or whatever and expecting to pass them to subprocesses which will be told the file descriptor numbers on the command line. (Let me say up front that I haven't such an example in my code at present; generally that kind of thing happens in shell script for me.) Let someone change the global default and suddening my library code doesn't work, and people will be tearing their hair out trying to figure out why; because the code was written correctly in the old regime. We have recently seen a mail thread where people were arguing (generally correctly IMO) that programs should not do chdir, because it changes a process wide global. The situation with cloexec is in my mind very analogous. So I am: +1 on offering a cloexec parameter, defaulting to False -0.5 on offering a global default that can be changed [...] | > - We should go over the standard library, and create FDs cloexec if | > they're not handed over to the caller, either because they're | > opened/closed before returning, or because the underlying file | > descriptor is kept private (not fileno() method, although it's | > relatively rare). That's the approach chosen by glibc, and it makes | > sense: if another thread forks() while a thread is in the middle of | > getpwnam(), you don't want to leak an open file descriptor to | > /etc/passwd (or /etc/shadow). | | I started to make cloexec explicit *everywhere* in the Python stdlib. | I reverted my commit because I think that only a few application start | child processes, so doing extra work (additionnal syscalls to set | cloexec) would slowdown Python for no gain. See my revert commit to | see how many functions need to be modified: | http://hg.python.org/features/pep-433/rev/963e450fc24f I see what you mean, it is a quite intrusive change. However, what you're mostly backing out is cloexec=True. There are therefore two changes coming here: - present the cloexec parameter - unilaterally make a global policy decision that in these many places in the stdlib, cloexec=True is now both correct/acceptable and _necessary_ If it were only the former, the change would be much smaller. | That's why I really like the idea of being able to configure the | default value of the cloexec parameter. By default: no overhead nor | backward compatibility issue. Whereas you can set close-on-exec flag | *everywhere* if you are concern by all issues of inheriting file | descriptors (cases listed in the PEP). Quietly breaking libraries that relied on cloexec=False being the status quo... The global tunable still make me feel uneasy. I am in agreement that the general leakage of open files because cloexec defaults to False is untidy and sometimes problematic; the tension here isn't lost on me:-) Cheers, -- Cameron Simpson <cs@zip.com.au> Out on the road, feeling the breeze, passing the cars. - Bob Seger

Hello,
You can actually count me in the cloexec=False camp, and against the idea of a configurable default value. Here's why: Why cloexec shouldn't be set by default: - While it's really tempting to fix one of Unix historical worst decisions, I don't think we can set file descriptors cloexec by default: this would break some applications (I don't think there would be too many of them, but still), but most notably, this would break POSIX semantics. If Python didn't expose POSIX syscalls and file descriptors, but only high-level file streams/sockets/etc, then we could probably go ahead, but now it's too late. Someone said earlier on python-dev that many people use Python for prototyping, and indeed, when using POSIX API, you expect POSIX semantics. Why the default value shouldn't be tunable: - I think it's useless: if the default cloexec behavior can be altered (either by a command-line flag, an environment variable or a sys module function), then libraries cannot rely on it and have to make file descriptors cloexec on an individual basis, since the default flag can be disabled. So it would basically be useless for the Python standard library, and any third-party library. So the only use case is for application writers that use raw exec() (since subprocess already closes file descriptors > 3, and AFAICT we don't expose a way to create processes "manually" on Windows), but there I think they fall into two categories: those who are aware of the problem of file descriptor inheritance, and who therefore set their FDs cloexec manually, and those who are not familiar with this issue, and who'll never look up a sys.setdefaultcloexec() tunable (and if they do, they might think: "Hey, if that's so nice, why isn't it on by default? Wait, it might break applications? I'll just leave the default then."). - But most importantly, I think such a tunable flag is a really wrong idea because it's a global tunable that alters the underlying operating system semantics. Consider this code: """ r, w = os.pipe() if os.fork() == 0: os.execve(['myprog']) """ With a tunable flag, just by looking at this code, you have no way to know whether the file descriptor will be inherited by the child process. That would be introducing an hidden global variable silently changing the semantics of the underlying operating system, and that's just so wrong. Sure, we do have global tunables: """ sys.setcheckinterval() sys.setrecursionlimit() sys.setswitchinterval() hash_randomization """ But those alter "extralinguistic" behavior, i.e. they don't affect the semantics of the language or underlying operating system in a way that would break or change the behavior of a "conforming" program. Although it's not as bad, just to belabor the point, imagine we introduced a new method: """ sys.enable_integer_division(boolean) Depending on the value of this flag, the division of two integers will either yield a floating point or truncated integer value. """ Global variables are bad, hidden global variables are worse, and hidden global variables altering language/operating system semantics are evil :-) What I'd like to see: - Adding a "cloexec" parameter to file descriptor creating functions/classes is fine, it will make it easier for a library/application writer to create file descriptors cloexec, especially in an atomic way. - We should go over the standard library, and create FDs cloexec if they're not handed over to the caller, either because they're opened/closed before returning, or because the underlying file descriptor is kept private (not fileno() method, although it's relatively rare). That's the approach chosen by glibc, and it makes sense: if another thread forks() while a thread is in the middle of getpwnam(), you don't want to leak an open file descriptor to /etc/passwd (or /etc/shadow). cf

On Fri, Jan 25, 2013 at 6:56 PM, Charles-François Natali <cf.natali@gmail.com> wrote:
It's a configurable setting in the same way that -Q makes the behaviour of "/" configurable in Python 2 (so your hypothetical example isn't hypothetical at all - it's a description the -Q option), and -R makes random hashing configurable in 2.7 and 3.2: it means we can change the default behaviour in a future version (perhaps Python 4) while allowing people to easily check if their code operates correctly in that state in the current version. I think the default behaviour needs to be configurable from the environment and the command line, but I don't believe it should be configurable from within the interpreter. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

sys.setdefaultcloexec() is convinient for unit test, but it may also be used by modules. A web framework may want to enable close-on-exec flag by default. The drawback of changing the default value after Python started is that Python may have created file descriptors before, so you cannot guarantee that all existing file descriptors have the flag set. Well, I don't know if sys.setdefaultcloexec() is a good idea or not :-) Victor

Le Fri, 25 Jan 2013 12:28:10 +0100, Victor Stinner <victor.stinner@gmail.com> a écrit :
Both Charles-François and Nick have good points. sys.setdefaultcloexec() is still useful if you want to force the policy from a Python script's toplevel (it's more practical than trying to fit a command-line option in the shebang line). Regards Antoine.

On Fri, Jan 25, 2013 at 9:36 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
Right, I'm only -0 on that aspect. It strikes me as somewhat dubious, but it's not obviously wrong the way a runtime equivalent of -Q or -R would be. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, Jan 25, 2013 at 10:07 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I just realised I could be converted to a +0 if the runtime time switch could only be used to set the global default as "cloexec=True" and couldn't be used to switch it back again (for testing purposes, if you only want to switch it on temporarily, use a subprocess). That way, as soon as you saw "sys.setdefaultcloexec()" at the beginning of __main__, you'd know descriptors would only be inherited when cloexec=False was set explicitly. If the default flag can also be turned off globally (rather than being a one-way switch), then you have no idea what libraries might do behind your back. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

2013/1/25 Nick Coghlan <ncoghlan@gmail.com>:
Oh, I like this idea. It does simplify many things :-) (And I agree that subprocess can be used to run a test which requires cloexec to be True by default.) -- I tried to be future-proof. If we decide to enable close-on-exec flag globally by default, how do you disable the flag globally? We may add an inverse command line option and another environment variable (ex: PYTHONNOCLOEXEC), but what about sys.setdefaultcloexec()? In a previous version, my implementation expected an argument for PYTHONCLOEXEC: PYTHONCLOEXEC=0 or PYTHONCLOEXEC=1. I realized that it's not how other options (like PYTHONDONTWRITEBYTECODE) are designed. But do we really want to enable close-on-exec in the future? Charles François has really good arguments against such choice :-) It's maybe better to consider that the default at startup will always be False. So we should only provide different ways to set the default to True. Victor

On Sat, Jan 26, 2013 at 12:54 AM, Victor Stinner <victor.stinner@gmail.com> wrote:
But do we really want to enable close-on-exec in the future? Charles François has really good arguments against such choice :-)
While many of Python's APIs are heavily inspired by POSIX, it still isn't POSIX. It isn't C either, especially in Python 3 (where C truncating division must be written as "//" and our internal text handling has made the migration to being fully Unicode based)
I think Charles François actually hit on a fairly good analogy by comparing this transition to the integer division one. To implement that: 1. In 2.x, the "-Q" option was introduced to allow the behaviour to be switched globally, while ensuring it remained consistent for the life of a given application 2. The "//" syntax was introduced to force the use of truncating integer division 3. "float(n) / d" could be used to force floating point division Significantly, code written using either option 2 or option 3 retains exactly the same semantics in Python 3, even though the default behaviour of "/" has now switched from truncating division to floating point division. Note also that in Python 3, there is *no* mechanism to request truncating division globally - if you want truncating division, you have to explicitly request it every time. So, if we agree that "cloexec-by-default" is a desirable goal, despite the inconsistency with POSIX (just as changing integer division was seen as desirable, despite the inconsistency with C), then a sensible transition plan becomes: 1. Introduce a mechanism to switch the behaviour globally, while ensuring it remains consistent for the life of a given application 2. Introduce the "cloexec=None/True/False" 3-valued parameter as needed to allow people to choose between default/definitely-cloexec/definitely-not-cloexec. 3. At some point in the future (perhaps in 3.5, perhaps in 4.0) switch the default behaviour to cloexec=True and remove the ability to change the behaviour globally. The reason I'd be +0 on a "one-way switch", even at runtime, is that you can just make it a no-op after that behaviour becomes the default. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

I don't plan to enable cloexec by default in a near future, nor in python 4. Someone else may change that later. Having a global flag should be enough. Enabling cloexec by default is interesting if *all* libraries respect the global flag (even C libraries), so subprocess can be used with close_fds=False. Closing all file descriptors is slow, especially on FreeBSD (it's now faster on linux, python lists /proc/pid/fd/). Victor

On 25Jan2013 21:07, Nick Coghlan <ncoghlan@gmail.com> wrote: | It's a configurable setting in the same way that -Q makes the | behaviour of "/" configurable in Python 2 (so your hypothetical | example isn't hypothetical at all - it's a description the -Q option), | and -R makes random hashing configurable in 2.7 and 3.2: it means we | can change the default behaviour in a future version (perhaps Python | 4) while allowing people to easily check if their code operates | correctly in that state in the current version. | | I think the default behaviour needs to be configurable from the | environment and the command line, but I don't believe it should be | configurable from within the interpreter. Hmm. This I can live with more happily, though I'm still uneasy. As an aside, I tend to feel that if something is tuneable it should be exposed within the interpreter. Maybe only in an exciting new module called shoot_self_in_foot or some similarly alarming name... Cheers, -- Cameron Simpson <cs@zip.com.au> That said, I'm inclined to agree that that's not necessarily a good idea. I always wanted to write a little program that would pop up a Mac window to ask ``I'm going to amputate a limb at random from you now.'' to see how many people would instinctively click "OK". - Marc VanHeyningen <mvanheyn@cs.indiana.edu>

On Sat, Jan 26, 2013 at 8:48 PM, Cameron Simpson <cs@zip.com.au> wrote:
I had missed this detail. I agree that it should be exposed in the interpreter. To my mind it is more like PYTHONPATH (which corresponds roughly to sys.path manipulations) than like -R (which changes something that should never be changed again, otherwise the sanity of the interpreter be at risk). It would seem hard to unittest the feature if it cannot be changed from within. But I can also think of other use cases for changing it from within (e.g. a script that decides on how to set it using a computation based on its arguments). -- --Guido van Rossum (python.org/~guido)

2013/1/27 Guido van Rossum <guido@python.org>:
sys.path is usually only used to add a new path, not to remove path from other libraries. I'm not sure that it's the best example to compare it to sys.setdefaultcloexec(). If sys.setdefaultcloexec() accepts an argument (so allow sys.setdefaultcloexec(False)), problems happen when two libraries, or an application and a library, disagree. Depending how/when the library is loaded, the flag may be True or False. I prefer to have a simple sys.setdefaultcloexec() which always set the flag to True. It's also simpler to explain how the default value is computed (it's less surprising). -- Unit tests can workaround the limitation using subprocesses. My implementation doesn't use sys.setdefaultcloexec() anymore, it just ensures that functions respect the current default value. I ran tests manually to test both default values (True and False). Tests may be improved later to test both defalut values using subprocess. Victor

On Sun, 27 Jan 2013 12:23:15 +0100 Victor Stinner <victor.stinner@gmail.com> wrote:
I don't think such limitations are very useful in practice. Users calling sys.setdefaultexec() will have to be sufficiently knowledgeable to understand the implications, anyway. Regards Antoine.

On Sun, Jan 27, 2013 at 9:29 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
I've yet to hear a use case for being able to turn it off globally if the application developer has indicated they want it on. A global flag that can be turned off programmatically is worse than no global flag at all. If we're never going to migrate to cloexec-on-by-default, then there simply shouldn't be a global flag - the option should just default to False. If we're going to migrate to cloexec-on-by-default some day, then the global flag should purely be a transition strategy to allow people to try out that future behaviour and adjust their application appropriately (by clearing the flag on descriptors that really need to be inherited). The typical default that should be assumed by library code will still be cloexec-off-by-default. A completely flexible global flag is just a plain bad idea for all the reasons Charles-François gave. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sun, Jan 27, 2013 at 3:45 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Out of context that last statement sounds pretty absurd. :-)
That doesn't seem to follow. We might never want to turn it on by default for reasons of compatibility with POSIX (whatever you say, POSIX semantics seep through Python in many places). But it might still be useful to be able to turn it on for specific programs (and doing that in code is a lot more convenient than having to say "run this app with python --cloexec", which sounds a real pain).
But typical library code won't care one way or another, will it?
A completely flexible global flag is just a plain bad idea for all the reasons Charles-François gave.
Honestly, what happened to the idea that we're all adults here? The likelihood that someone is clueless enough to misuse the flag and yet clueful enough to find out how to change it seems remote -- and they pretty much deserve what they get. It's like calling socket.settimeout(0.1) and then complaining that urllib.urlopen() raises exceptions, or calling sys.setrecursionlimit(1000000) when you are getting StackOverflowError. The only reason I can see why a flag should never be changeable after the fact is when it would end up violating the integrity of the interpreter. But I don't see that here. I just see paranoia about protecting users from all possible security issues. That seems a hopeless quest. That said, I'm sure I'll survive if you ignore my opinion, so I explicitly give you that option -- in the grand scheme of things I can't care that much about this and it seems a mostly theoretical issue. (And, an attacker who can exploit file descriptors given to it can probably find plenty of other attacks on a system they've broken into.) -- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
It's like calling socket.settimeout(0.1) and then complaining that urllib.urlopen() raises exceptions
but that's not what's happening. you'll see urllib.urlopen raising exceptions and only afterwards realize that you called into some third party library code that decided to change the timeout.

Yeah, so the answer to all this is that 3rd party libraries know better than to mess with global settings. On Mon, Jan 28, 2013 at 1:27 PM, Ralf Schmitt <ralf@systemexit.de> wrote:
-- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
Yeah, so the answer to all this is that 3rd party libraries know better than to mess with global settings.
Right. But why make it configurable at runtime then? If you're changing the value, then you're the one probably breaking third party code.

Sigh. This is getting exasperating. There's other code that might want to change this besides 3rd party library code. E.g. app configuration code. On Mon, Jan 28, 2013 at 1:56 PM, Ralf Schmitt <ralf@systemexit.de> wrote:
-- --Guido van Rossum (python.org/~guido)

Guido van Rossum <guido@python.org> writes:
So, third party library code should know better, while at the same time it's fine to mess with global settings from app configuration code. That does not make sense.

On 01/28/2013 04:17 PM, Ralf Schmitt wrote:
Library code should not be relying on globals settings that can change. Library code should be explicit in its calls so that the current value of a global setting is irrelevant. ~Ethan~ P.S. Unless, of course, the library is meant to work with that global, and can work correctly with all of the global's valid settings.

That's one of the problems I've raised with this global flag since the beginning: it's useless for libraries, including the stdlib (and, as a reminder, this PEP started out of a a bug report against socket inheritance in socketserver). And once again, it's an hidden global variable, so you won't be able any more to tell what this code does: """ r, w = os.pipe() if os.fork() == 0: os.close(w) os.execve(['myprog']) """ Furthermore, if the above code is part of a library, and relies upon 'r' FD inheritance, it will break if the user sets the global cloexec flag. And the fact that a library relies upon FD inheritance is an implementation detail, the users shouldn't have to wonder whether enabling a global flag (in their code, not in a library) will break a given library: the only alternative for such code to continue working would be to pass cloexec=True explicitly to os.pipe()... The global socket.settimeout() is IMO a bad idea, and shouldn't be emulated. So I'm definitely -1 against any form of tunable value (be it a sys.setdefaultcloexec(), an environment variable or command-line flag), and still against changing the default value. But I promise that's the last time I'm bringing those arguments up, and I perfectly admit that some people want it as much as I don't want it :-) cf

On Tue, Jan 29, 2013 at 4:13 PM, Charles-François Natali <cf.natali@gmail.com> wrote:
I now think the conservative option is to initially implement the design in 3.4 without an option to change the global default. The "cloexec=True/False" model at least *supports* a tunable default, as well as eventually *changing* the default if we choose to do so, but I don't think there's any hurry to proceed to those steps. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Library code should not be relying on globals settings that can change.
Did you try my implementation of the PEP 433 on your project? Did you review my patch implementing the PEP 433? http://hg.python.org/features/pep-433 http://bugs.python.org/issue17036 I expected more change, whereas only very few modules of the stdlib need changes to support both modes (cloexec=False or cloexec=True by default). I made 5 different types of changes. Only th change (3) gives you an idea of how many modules must be fixed to support cloexec=True by default: 4 modules (http.server, multiprocessing, pty, subprocess) on a total of something like 200 modules (stdlib). And I'm not sure that all these modules need to be modified, I have to check again. By the way, I chose to not consider file descriptors 0, 1 and 2 as special: the developer must disable cloexec explicitly for standard streams. If we consider them as special, fewer (or no) modules would require changes. -- (1) add a cloexec parameter, modified modules: * io * asyncore * socket (2) explicitly enable cloexec: usually for security reasons, don't leak a file descriptor in a child process => it's not directly related to this PEP, I should maybe do this in a different commit. Said differently: code works with cloexec enabled or disabled, but I consider that enabled is safer. * cgi * getpass * importlib * logging * os * pty * pydoc * shutil (3) explicitly disable cloexec: code doesn't work if cloexec is set (code relies on file descriptor inherance); modified modules: * http.server: CGI uses dup2() to replace stdin and stdout * multiprocessing: stdin is replaced with /dev/null * pty: dup2() to replace stdin, stdout, stderr * subprocess: dup2() to replace stdin, stdout, stderr (4) refactoring to use the new cloexec parameter: * tempfile (5) apply the default value of the cloexec parameter (in C modules): * curses * mmap * oss * select * random Victor

On Tue, 29 Jan 2013 01:17:35 +0100 Ralf Schmitt <ralf@systemexit.de> wrote:
Yes, it's fine, because an application developer can often control (or at least study) the behaviour of all the code involved. It's the same story as with logging configuration and similar process-wide settings. Libraries shouldn't mess with it but the top-level application definitely can (and should, even). (and if you think many third-party libraries call fork()+exec() as part as their normal duty, then I've got a bridge to sell you) Regards Antoine.

Antoine Pitrou <solipsis@pitrou.net> writes:
Yes, it's fine, because an application developer can often control (or at least study) the behaviour of all the code involved.
I'd rather not have to check if some library messes with that global setting and work around it if it does! The fact that you can control and work around a global setting that may change, isn't a reason to introduce that global setting and the increased complexity that comes with it.
That's a bad comparison, because the situation is quite different with the logging module. Configuration of the logging module does not change the behaviour for code calling into the logging module (well, besides log output being produced somewhere, but that doesn't matter for the caller).

Antoine Pitrou <solipsis@pitrou.net> writes:
I was talking about some library changing the flag. Not changing the flag myself doesn't help in that case. I'll probably have to explicitly pass a cloexec argument to each fd creating function I'm calling in order to not be dependent on the global setting. Please just acknowledge that having a global configurable setting may lead to problems. Why is sys.setdefaultencoding being hidden in python 2?

Le Tue, 29 Jan 2013 11:24:49 +0100, Ralf Schmitt <ralf@systemexit.de> a écrit :
Please just acknowledge that having a global configurable setting may lead to problems.
Ralf, I won't "acknowledge" anything just so that it makes you feel better. Your point has been made and has also been rebutted. Let's agree to disagree and move along. Regards Antoine.

2013/1/25 Charles-François Natali <cf.natali@gmail.com>:
You can actually count me in the cloexec=False camp, and against the idea of a configurable default value.
Oh ok.
Agreed.
In my experience, in most cases, the default value of cloexec just doesn't matter at all. If your program relies on the state of the close-on-exec flag: you have to make it explicit, specify cloexec paramater. Example: os.pipe(cloexec=True). If you don't modify your application, it will just not work using -e command line option (or PYTHONCLOEXEC environment variable). But why would you enable cloexec by default if your application is not compatible?
The problem is that Python can be embeded in application: the application can start a child process independently of Python.
If this snippet doesn't work with cloexec enabled by default, you have to write: os.pipe(cloexec=False).
I started to make cloexec explicit *everywhere* in the Python stdlib. I reverted my commit because I think that only a few application start child processes, so doing extra work (additionnal syscalls to set cloexec) would slowdown Python for no gain. See my revert commit to see how many functions need to be modified: http://hg.python.org/features/pep-433/rev/963e450fc24f That's why I really like the idea of being able to configure the default value of the cloexec parameter. By default: no overhead nor backward compatibility issue. Whereas you can set close-on-exec flag *everywhere* if you are concern by all issues of inheriting file descriptors (cases listed in the PEP). In the stdlib, I only specified cloexec parameter where it was required to ensure that it works with any default value. Only a few modules were modified (subprocess, multiprocessing, ...). Victor

On 25Jan2013 12:24, Victor Stinner <victor.stinner@gmail.com> wrote: | 2013/1/25 Charles-François Natali <cf.natali@gmail.com>: | > You can actually count me in the cloexec=False camp, and against the | > idea of a configurable default value. | | Oh ok. I'm leaning to this view myself also. | > Why the default value shouldn't be tunable: | > - I think it's useless: if the default cloexec behavior can be altered | > (either by a command-line flag, an environment variable or a sys | > module function), then libraries cannot rely on it and have to make | > file descriptors cloexec on an individual basis, since the default | > flag can be disabled. | | In my experience, in most cases, the default value of cloexec just | doesn't matter at all. If your program relies on the state of the | close-on-exec flag: you have to make it explicit, specify cloexec | paramater. Example: os.pipe(cloexec=True). If you don't modify your | application, it will just not work using -e command line option (or | PYTHONCLOEXEC environment variable). But why would you enable cloexec | by default if your application is not compatible? Because we're not all writing applications. I write a lot of small library routines and then use them in my apps. On a UNIX system I can plow on presuming cloexec=False, and will feel no desire to label it in any calls. And so I might be making pipes or whatever and expecting to pass them to subprocesses which will be told the file descriptor numbers on the command line. (Let me say up front that I haven't such an example in my code at present; generally that kind of thing happens in shell script for me.) Let someone change the global default and suddening my library code doesn't work, and people will be tearing their hair out trying to figure out why; because the code was written correctly in the old regime. We have recently seen a mail thread where people were arguing (generally correctly IMO) that programs should not do chdir, because it changes a process wide global. The situation with cloexec is in my mind very analogous. So I am: +1 on offering a cloexec parameter, defaulting to False -0.5 on offering a global default that can be changed [...] | > - We should go over the standard library, and create FDs cloexec if | > they're not handed over to the caller, either because they're | > opened/closed before returning, or because the underlying file | > descriptor is kept private (not fileno() method, although it's | > relatively rare). That's the approach chosen by glibc, and it makes | > sense: if another thread forks() while a thread is in the middle of | > getpwnam(), you don't want to leak an open file descriptor to | > /etc/passwd (or /etc/shadow). | | I started to make cloexec explicit *everywhere* in the Python stdlib. | I reverted my commit because I think that only a few application start | child processes, so doing extra work (additionnal syscalls to set | cloexec) would slowdown Python for no gain. See my revert commit to | see how many functions need to be modified: | http://hg.python.org/features/pep-433/rev/963e450fc24f I see what you mean, it is a quite intrusive change. However, what you're mostly backing out is cloexec=True. There are therefore two changes coming here: - present the cloexec parameter - unilaterally make a global policy decision that in these many places in the stdlib, cloexec=True is now both correct/acceptable and _necessary_ If it were only the former, the change would be much smaller. | That's why I really like the idea of being able to configure the | default value of the cloexec parameter. By default: no overhead nor | backward compatibility issue. Whereas you can set close-on-exec flag | *everywhere* if you are concern by all issues of inheriting file | descriptors (cases listed in the PEP). Quietly breaking libraries that relied on cloexec=False being the status quo... The global tunable still make me feel uneasy. I am in agreement that the general leakage of open files because cloexec defaults to False is untidy and sometimes problematic; the tension here isn't lost on me:-) Cheers, -- Cameron Simpson <cs@zip.com.au> Out on the road, feeling the breeze, passing the cars. - Bob Seger
participants (10)
-
Antoine Pitrou
-
Cameron Simpson
-
Charles-François Natali
-
Ethan Furman
-
Guido van Rossum
-
Nick Coghlan
-
R. David Murray
-
Ralf Schmitt
-
Ralf Schmitt
-
Victor Stinner