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: Different 3.0a1 exit behavior
Type: Stage:
Components: Interpreter Core Versions: Python 3.0
process
Status: closed Resolution: accepted
Dependencies: Superseder:
Assigned To: gvanrossum Nosy List: MrJean1, christian.heimes, gvanrossum, nnorwitz
Priority: high Keywords:

Created on 2007-10-25 23:07 by MrJean1, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
dlibtest.c MrJean1, 2007-10-26 20:22
dlibtest.c MrJean1, 2007-10-27 06:03
stdout-close.patch nnorwitz, 2007-10-27 06:20
dlibtest4.c MrJean1, 2007-10-28 17:44
py3k_closefd.patch christian.heimes, 2007-10-29 09:03
py3k_combined_preliminary_closefd.patch christian.heimes, 2007-10-30 06:33
py3k_preliminary_stderr3.patch christian.heimes, 2007-10-30 18:10
Messages (39)
msg56761 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-25 23:07
Python 3.0a1 on Linux and MacOS X 10.4.10 exits differently than Python 
2.4 and 2.5.

With previous Python binaries the destructor** function of any pre-
loaded shared library is called prior to exit.  With Python 3.0a1 it is 
not, neither when exiting Python from the command line with Ctrl-D nor 
when using exit().

A workaround is to install a SIGABRT signal handler from the library and 
exit Python with os.abort().

Python 3.0a1 was built from source using the standard build sequence 
without any ./configure options except --prefix. 

---
**) defined with GNU __attribute__((destructor)).  The shared library is  
loaded through environment variable LD_PRELOAD on Linux and 
DYLD_INSERT_LIBRARIES on MacOS X.
msg56779 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2007-10-26 06:28
Thanks for testing 3.0.  

Do you have any idea why they are no longer called?  I don't recall any
changes related to this area.  Can you try to debug further?
msg56792 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 15:28
Here is one thought, maybe 3.0a calls _exit() while 2.x uses exit() to 
terminate.  With _exit() any functions installed with atexit() or 
on_exit() are *not* called.

That would explain the difference but only if destructor functions are 
installed with atexit() or on_exit().  I do not know whether that.
msg56793 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 15:30
Sorry, premature submit.  I will try using atexit() and report what 
happens there.
msg56796 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 17:18
Using atexit() to install the destructor function does not help.  The 
function in not called when 3.0a1 exits, but with 2.5.1 it is.  Same 
behavior on Linux and MacOS X.

Btw, that would mean that any C extension which uses atexit() directly 
may be affected by this issue.

Running python with the debugger shows that 3.0a1 and 2.5.1 both exit 
thru exit() and not _exit().  A breakpoint at _exit is hit, but the call 
originates from exit and not anywhere in the python binary.

There is a new atexitmodule.c in 3.0a1 which did not exits in 2.5.1.  
But that is handling the atexit functionality at the Python level and 
not C.

This man page <http://linux.die.net/man/3/atexit> mentions that all 
registered atexit functions are removed after a fork+exec.  But 
breakpoints set at fork, fork1, forkpty and vfork are never hit by 
3.0a1.

That is as far as I got.
msg56802 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-26 17:47
Can you provide a very small shared library that demonstrates this
problem? (E.g. you could start by modifying Modules/xxmodule.c, adding a
'destructor' function.)
msg56810 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 19:10
Yes, I will make a small library.  But first, here is another piece of 
evidence.

As I mentioned, using std atexit does not work on 3.0a1.  But 
surprisingly, the destructor function is not called either when 
installed with Py_AtExit on 3.0a1.  On 2.5.1 it is.

There must something in Py_Terminate or Py_Finalize which is different 
in 3.0a1.
msg56818 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 20:03
Attached is a simple test case which demonstrates the problem on Linux 
and MacOS X.  It is not an xxmodule example, though and hope this is OK.

See the comments for build steps and example output with 3 different 
Python builds on both platforms.

If you need another test case which uses Py_AtExit, let me know.
msg56820 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 20:22
Here is the same file with an #if to use to Py_AtExit or destructor case.   
Please us this one instead of the earlier one.
msg56821 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-26 20:30
> Here is the same file with an #if to use to Py_AtExit or destructor case.
> Please us this one instead of the earlier one.
>
> Added file: http://bugs.python.org/file8622/dlibtest.c

I can build it just fine on Ubuntu dapper, but I can't run it.  The
command given in the comment fails immediately:

$ env LD_PRELOAD  dlibtest.so  ~/p3/python
env: LD_PRELOAD: No such file or directory
$

When I modify it slightly I get another error:

$ env LD_PRELOAD=dlibtest.so  ~/p3/python
ERROR: ld.so: object 'dlibtest.so' from LD_PRELOAD cannot be preloaded: ignored.
Python 3.0a1+ (py3k, Oct 26 2007, 12:30:11)
[GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> ^D
$
msg56823 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 21:02
My fault, sorry.  The command line to run should be

  env  LD_PRELOAD=./dlibtest.so  ...

i.e. with '=' sign and no spaces.  And the library file may have to be 
an absolute path.
msg56824 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-26 21:16
OK, confirmed. But no insignt in what happened yet... Do you know where
the atexit stuff happens in 2.5?
msg56825 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 21:30
The Py_AtExit function is in Python/pythonrun.c.  The calls to all 
installed C functions are made in  call_ll_exitfuncs, also in 
pythonrun.c.  The call to  call_ll_exitfuncs is at the very end of 
Py_Finalize also in pythonrun.c.

I am just getting down there now and Py_Finalize is called and reaches 
the call to PyGrammar_RemoveAccelerators a few lines higher.  But 
call_ll_exitfuncs is not called, as far as I can tell.
msg56826 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 21:38
One more thing.  Stepping with the debugger thru Py_Finalize looks exactly 
the same for 2.5.1 and 3.0a1 in the last part of that function.
msg56827 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 22:20
I put a bunch of printf's in the Py_Finalize function of 2.5.1 and 
3.0a1.

All those show up when 2.5.1 exists including the call to 
call_ll_exitfuncs.

But in 3.0a1 only a few show up and the last one is just before the call 
to PyImport_Cleanup near line 393.  It looks like that call never 
returns.  That call never returns, it seems.
msg56831 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2007-10-26 23:28
Maybe that's because site and io get cleaned up and then there is some
fatal error that can't be printed?
msg56832 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-26 23:55
This is quite bizarre and difficult to reproduce.  With gdb, 3.0a1 does 
get to the very end of Py_Finalize, but without gdb it doesn't.

Also, the call to static function  call_all_exitfuncs is inlined, its 
while loop is shown inside Py_Finalize on MacOS X.  On Linux that is not 
visible, probably due to differences in gcc/gdb, 4.0.1 or MacOS X and 
3.4.6 on Linux.
msg56834 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2007-10-27 00:01
I suggest you configure --with-pydebug.  That will disable
optimization, enable debugging symbols and will make it easier to
develop.  Note that a debug version runs much slower due to asserts()
and other internal changes.  Otherwise, it should work the same.

On Oct 26, 2007 4:55 PM, Jean Brouwers <report@bugs.python.org> wrote:
>
> Jean Brouwers added the comment:
>
> This is quite bizarre and difficult to reproduce.  With gdb, 3.0a1 does
> get to the very end of Py_Finalize, but without gdb it doesn't.
>
> Also, the call to static function  call_all_exitfuncs is inlined, its
> while loop is shown inside Py_Finalize on MacOS X.  On Linux that is not
> visible, probably due to differences in gcc/gdb, 4.0.1 or MacOS X and
> 3.4.6 on Linux.
>
>
> __________________________________
> Tracker <report@bugs.python.org>
> <http://bugs.python.org/issue1329>
> __________________________________
>
msg56835 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 00:09
OK, I try that.
msg56838 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 00:28
The 3.0a1 build --with-pydebug behaves the same as before (on Linux).  The 
problem does occur without gdb but not with gdb.
msg56840 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 01:49
It looks like the problem may indeed just be that I/O is being shut down 
somewhere inside PyImport_Cleanup.  I am modifying the test case to 
demonstrate that and will submit that version as soon as it runs.
msg56844 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 06:03
Attached is an updated dlibtest.c file.  It prints a message in the con-
/destructor functions and if that fails it calls _exit(9).

Compile and run it as before and check the exit status.  If the latter 
is 9 or 011, a printf error occurred indicating e.g. that stdout was 
closed or something similar.

This version can also be used with gdb, either by pre-loading the 
dlibtest.so library within gdb or before invoking gdb.  To preload the 
library within gdb (on Linux) use

   gdb  .../python
   (gdb) set environment LD_PRELOAD ./dlibtest.so
   (gdb) run
   .....

or to preload before gdb use

   setenv LD_PRELOAD ./dlibtest.so
   gdb .../python
   (gdb) run
   .....

Lastly, my previous observations about this issue were clearly a "trompe 
d'oeil", especially my statement that PyImport_Cleanup never returned.  
The missing print statements *after* the PyImport_Cleanup call are 
simply due to printf errors, and nothing else ;-)
msg56845 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2007-10-27 06:20
When I run with the attached patch, I see the message: 

*** dtor called in python ...

Is that the behavior you expect?
msg56854 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 15:12
Yes, that is the expected behavior in this case.
msg56856 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 16:41
One final comment as confirmation.  If the messages are written to a file, 
other than stdout and stderr, they do appear in unpatched 3.0a1 and 3.0a1 
behaves as expected.  The root cause of the problem was the closed stdout.
msg56857 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-27 17:04
So is there even a bug? Arguably you shouldn't be writing anything that
late in the life of a shared library.
msg56859 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-27 18:48
It is quite common to pre-load libraries into existing binaries e.g. for 
profiling  Typically, those write to stdout or stderr only if the option 
for an output file is not used.  That is how I happened to run into the 
issue the other day.

For a while, the initial symptoms looked like the different exit 
behavior in 3.0a1 might be a serious problem.  It was not obvious that  
stdout and -err might have been closed and caused the difference.  All 
the Python 2.x versions never closed stdout and -err.

Therefore, 3.0 should probably not do that either.  But that is really 
your call.
msg56882 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-28 15:31
One more argument.  Without a fix, 3.0 would not even print a C debug 
message from a destructor function nor from any function installed with 
atexit or Py_AtExit.  The dlibtest shows that for 2 of these 3.
msg56885 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2007-10-28 17:03
Can you try this patch, please? It has the same effect as the other
patch from Neal but it doesn't loose ref counts. I've patched the
dealloc function of _FileIO to keep fd 1 and fd 2 open.

Index: Modules/_fileio.c
===================================================================
--- Modules/_fileio.c   (Revision 58699)
+++ Modules/_fileio.c   (Arbeitskopie)
@@ -270,7 +270,8 @@
        if (self->weakreflist != NULL)
                PyObject_ClearWeakRefs((PyObject *) self);

-       if (self->fd >= 0) {
+       /* Don't close stdout and stderr */
+       if (self->fd == 0 || self->fd > 2) {
                errno = internal_close(self);
                if (errno < 0) {
 #ifdef HAVE_STRERROR
msg56886 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-28 17:44
I could not try Neal's patch since it does not seem to apply to the 
3.0a1 source I have.  But the Modules/_fileio.c patch works just fine on 
my Linux and MacOS X.  Here is the Linux result: 

$ env  LD_PRELOAD=./dlibtest4.so  ~/Python-3dbg/python
*** ctor called in python ...
*** atexit OK in python ...
Python 3.0a1 (py3k, Oct 28 2007, 10:23:59) 
[GCC 3.4.6 20060404 (Red Hat 3.4.6-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
[36000 refs]
[21985 refs]
*** dtor called in python ...
$

Most interesting is that this Python build --with-pydebug now prints 2 
lines in [..] brackets on exit.  That 2nd line, [21985 refs] never 
showed up before!

Also, attached is another version of my test case renamed to dlibtest4.  
It includes 4 different use cases.  More details inside.
msg56890 - (view) Author: Jean Brouwers (MrJean1) Date: 2007-10-28 19:08
Perhaps, the proper behavior is the following.

After calling all functions/methods installed at the Python level with 
atexit.register and all C functions installed with Py_AtExit, the 
objects sys.stdin, sys.stdout and sys.stderr are destroyed.

However, the C level files stdin, stdout and stderr are *never* closed 
by Python.  Those will be closed (like any other open file) after all 
functions installed with atexit or defined as destructors have been 
called.
msg56897 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-29 00:57
Right. I think the right solution is to add an option to _FileIO that
says "don't close the filedescriptor when close() is called". This
option should only be allowed when the "filename" argument is an
integer file descriptor. It should be passed when stdin/out/err are
created. It may also be helpful in some other places?!
msg56898 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2007-10-29 09:03
Here you go, Guido!
msg56944 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-30 00:42
Thanks!!  Code review:

Shouldn't closefd be passed as 1 in import.c?

I don't see the point of distinguishing between -1 and +1.  The block
"if (closefd < 0) { closefd = 1; }" looks rather silly.

In io.py, you should document that closefd must not be False when a
filename is given.

I think in _fileio.c, you can insist that the closefd argument is an int
(a bool will work anyway, as bool is a subclass of int).

I don't think we should warn when trying to close an unclosable fd; it
should really just be a no-op.  Also, if you are going to call
PyErr_WarnEx(), you should test its return value (it can raise an
exception!).

Please don't add trailing whitespace.
msg56957 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2007-10-30 06:33
Guido van Rossum wrote:
> Shouldn't closefd be passed as 1 in import.c?
> 
> I don't see the point of distinguishing between -1 and +1.  The block
> "if (closefd < 0) { closefd = 1; }" looks rather silly.

I used -1 as default to keep it consistent with buffer=-1. I figured out
that I can go with "closefd != 0 means close it".

> In io.py, you should document that closefd must not be False when a
> filename is given.

Done

> I think in _fileio.c, you can insist that the closefd argument is an int
> (a bool will work anyway, as bool is a subclass of int).

Thanks, it makes the code a bit easier.

> I don't think we should warn when trying to close an unclosable fd; it
> should really just be a no-op.  Also, if you are going to call
> PyErr_WarnEx(), you should test its return value (it can raise an
> exception!).

I think we should keep the warning. The warning made me aware of a minor
bug in quopri.

> Please don't add trailing whitespace.

I've reconfigured my editor to remove trailing spaces.

I've attached a combined patch for closefd and preliminary stderr.

Christian
msg56963 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-30 17:19
OK, thanks.  The closefd part is good, but the stderrprinter part has a
problem.  On Linux, in a non-debug build, this has the odd side effect
of subtracting one from sys.maxunicode.  In a debug build, it dies like
this: 

$ ./python -S
python: Modules/gcmodule.c:336: visit_reachable: Assertion `gc_refs > 0
|| gc_refs == (-3) || gc_refs == (-2)' failed.
Aborted
$ 

If I comment out the PySys_SetObject() call everything seems fine, but I
suspect that the problem is actually in the creation of the stdprinter
object.
msg56964 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-30 17:28
I've checked the closefd patch (which minor changes) into the py3k branch.

Committed revision 58711.

Please take the stdprinter patch to the original issue (bug 1352).
msg56969 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2007-10-30 18:06
Guido van Rossum wrote:
> Guido van Rossum added the comment:
> 
> OK, thanks.  The closefd part is good, but the stderrprinter part has a
> problem.  On Linux, in a non-debug build, this has the odd side effect
> of subtracting one from sys.maxunicode.  In a debug build, it dies like
> this: 
> 
> $ ./python -S
> python: Modules/gcmodule.c:336: visit_reachable: Assertion `gc_refs > 0
> || gc_refs == (-3) || gc_refs == (-2)' failed.
> Aborted
> $ 
> 
> If I comment out the PySys_SetObject() call everything seems fine, but I
> suspect that the problem is actually in the creation of the stdprinter
> object.

I may have found the problem. I forgot th remove Py_TPFLAGS_HAVE_GC from
tp_flags. It's a relict from my first implementation.

$ ./python
Fatal Python error: Py_Initialize: can't initialize sys standard streams
Traceback (most recent call last):
  File "/home/heimes/dev/python/py3k/Lib/io.py", line 22, in <module>
    test
NameError: name 'test' is not defined
Aborted

$ vi Lib/io.py

$ ./python -S
Python 3.0a1+ (py3k:58715M, Oct 30 2007, 19:02:47)
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
>>> import sys
[33116 refs]
>>> sys.maxunicode
1114111
[33127 refs]
>>>
[33128 refs]
[23233 refs]

$ python2.5 -c "import sys; print sys.maxunicode"
1114111
msg56975 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-10-30 18:37
Thanks, I've closed issue 1352 too now.
History
Date User Action Args
2022-04-11 14:56:27adminsetgithub: 45670
2007-10-30 18:37:53gvanrossumsetmessages: + msg56975
2007-10-30 18:10:01christian.heimessetfiles: + py3k_preliminary_stderr3.patch
2007-10-30 18:06:44christian.heimessetmessages: + msg56969
2007-10-30 17:28:37gvanrossumsetstatus: open -> closed
resolution: accepted
messages: + msg56964
2007-10-30 17:19:01gvanrossumsetmessages: + msg56963
2007-10-30 06:33:20christian.heimessetfiles: + py3k_combined_preliminary_closefd.patch
messages: + msg56957
2007-10-30 00:42:26gvanrossumsetpriority: high
messages: + msg56944
2007-10-29 20:23:52gvanrossumsetassignee: gvanrossum
2007-10-29 09:03:33christian.heimessetfiles: + py3k_closefd.patch
messages: + msg56898
2007-10-29 00:57:23gvanrossumsetmessages: + msg56897
2007-10-28 19:08:08MrJean1setmessages: + msg56890
2007-10-28 17:44:21MrJean1setfiles: + dlibtest4.c
messages: + msg56886
2007-10-28 17:03:58christian.heimessetnosy: + christian.heimes
messages: + msg56885
2007-10-28 15:31:17MrJean1setmessages: + msg56882
2007-10-27 18:48:10MrJean1setmessages: + msg56859
2007-10-27 17:04:41gvanrossumsetmessages: + msg56857
2007-10-27 16:41:20MrJean1setmessages: + msg56856
2007-10-27 15:12:54MrJean1setmessages: + msg56854
2007-10-27 06:20:09nnorwitzsetfiles: + stdout-close.patch
messages: + msg56845
2007-10-27 06:03:49MrJean1setfiles: + dlibtest.c
messages: + msg56844
2007-10-27 01:49:40MrJean1setmessages: + msg56840
2007-10-27 00:28:18MrJean1setmessages: + msg56838
2007-10-27 00:09:02MrJean1setmessages: + msg56835
2007-10-27 00:01:32nnorwitzsetmessages: + msg56834
2007-10-26 23:55:07MrJean1setmessages: + msg56832
2007-10-26 23:28:13nnorwitzsetmessages: + msg56831
2007-10-26 22:20:20MrJean1setmessages: + msg56827
2007-10-26 21:38:42MrJean1setmessages: + msg56826
2007-10-26 21:30:45MrJean1setmessages: + msg56825
2007-10-26 21:16:34gvanrossumsetmessages: + msg56824
2007-10-26 21:15:56gvanrossumsetfiles: - dlibtest.c
2007-10-26 21:02:18MrJean1setmessages: + msg56823
2007-10-26 20:30:11gvanrossumsetmessages: + msg56821
2007-10-26 20:22:44MrJean1setfiles: + dlibtest.c
messages: + msg56820
2007-10-26 20:03:50MrJean1setfiles: + dlibtest.c
messages: + msg56818
2007-10-26 19:10:13MrJean1setmessages: + msg56810
2007-10-26 17:47:53gvanrossumsetnosy: + gvanrossum
messages: + msg56802
2007-10-26 17:18:28MrJean1setmessages: + msg56796
2007-10-26 15:30:26MrJean1setmessages: + msg56793
2007-10-26 15:28:31MrJean1setmessages: + msg56792
2007-10-26 06:28:16nnorwitzsetnosy: + nnorwitz
messages: + msg56779
2007-10-25 23:07:47MrJean1create