classification
Title: Exception on IDLE closing
Type: behavior Stage: resolved
Components: IDLE Versions: Python 3.5, Python 3.4, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: terry.reedy Nosy List: RusiMody, kbk, python-dev, roger.serwy, serhiy.storchaka, taleinat, terry.reedy
Priority: high Keywords: 3.4regression, patch

Created on 2014-01-07 20:57 by serhiy.storchaka, last changed 2014-10-11 22:23 by terry.reedy. This issue is now closed.

Files
File name Uploaded Description Edit
taleinat_idle_closing_exception.patch taleinat, 2014-02-05 10:33 Patch to fix the bug by catching and ignoring the raised exception. BROKEN: causes import error. review
taleinat_idle_closing_exception_2.patch taleinat, 2014-02-05 12:44 New patch with the try/except inside the loop and added comment. BROKEN: causes import error. review
taleinat_idle_closing_exception_3.patch taleinat, 2014-02-05 12:50 Patch which fixes the bug. Tested. review
Messages (28)
msg207599 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-01-07 20:57
When run IDLE with file name as agument

$ ./python -m idlelib.idle Lib/decimal.py

and then close or exit it, following traceback is printed:

Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0xb5ead62c>>
Traceback (most recent call last):
  File "/home/serhiy/py/cpython/Lib/idlelib/MultiCall.py", line 230, in __del__
  File "/home/serhiy/py/cpython/Lib/tkinter/__init__.py", line 1043, in unbind
_tkinter.TclError: can't invoke "bind" command:  application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0xb5eab92c>>
Traceback (most recent call last):
  File "/home/serhiy/py/cpython/Lib/idlelib/MultiCall.py", line 230, in __del__
  File "/home/serhiy/py/cpython/Lib/tkinter/__init__.py", line 1043, in unbind
_tkinter.TclError: can't invoke "bind" command:  application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0xb5ea4e4c>>
Traceback (most recent call last):
  File "/home/serhiy/py/cpython/Lib/idlelib/MultiCall.py", line 230, in __del__
  File "/home/serhiy/py/cpython/Lib/tkinter/__init__.py", line 1043, in unbind
_tkinter.TclError: can't invoke "bind" command:  application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0xb5ea46ec>>
Traceback (most recent call last):
  File "/home/serhiy/py/cpython/Lib/idlelib/MultiCall.py", line 230, in __del__
  File "/home/serhiy/py/cpython/Lib/tkinter/__init__.py", line 1043, in unbind
_tkinter.TclError: can't invoke "bind" command:  application has been destroyed

This is occurred only in 3.4.
msg210297 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-05 10:24
Confirmed on OSX 10.8 with Python 3.4 (built from default branch, changeset 88969:32af4954e46a).

Note that this doesn't happen when opening just a shell window, e.g. by running ./python -m idlelib.idle. Furthermore, even when running IDLE as described in the OP, if I open a shell window and then close, I don't get an exception.
msg210298 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-05 10:33
This is caused by MultiCall's _ComplexBinder.__del__() being called during app shutdown. _ComplexBinder.__del__() unbinds a bunch of event handlers from the widget to which it is attached. It seems that there's some edge case here where the underlying Tk widget has already been destroyed.

Instead of trying to debug all of the Tk events and app shutdown order, I propose just surrounding this __del__() code with a try/except block, catching _tkinter.TclError and ignoring it. From my (somehwat limited) understanding of MultiCall, this shouldn't do any harm.

See attached patch.
msg210300 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-02-05 11:18
1. try/except should be inside a loop, not outside.
 
2. It would be better not to hide the problem under the rug, but find why the order of destruction is different when open shell window. Of course, if better solution will not be found, we will have to apply this patch before 3.4 releasing.
msg210304 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-05 11:43
I put the try/except outside of the loop on purpose. If calling the widget's unbind() method fails once, it will fail forever afterwards.

If you prefer a different spelling, move the try/except into the loop, and break out of the loop in case of an exception:

for seq, id in self.handlerids:
    try:
        self.widget.unbind(self.widgetinst, seq, id)
    except _tkinter.TclError:
        break


As for avoiding the exception in the first place, I'm sure figuring out how IDLE shuts down would be a lot of work, and I honestly don't think it's necessary. Note that this problem is triggered in a __del__() method; making sure these are called at a proper time is problematic because the timing depends on the GC.
msg210307 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-05 12:44
Attaching second patch, to replace the first.

As suggested by Serhiy, I moved the try/except block into the loop.

I also added a comment explaining the try/except block and referencing this issue.
msg210308 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-05 12:50
Both previous patches caused an import error. Here's a new patch with that fixed, and actually tested to fix the bug.
msg210312 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-02-05 13:38
I tried all three versions both installed and recent repository builds and verified that the overt issue is limited to 3.4. I agree that the exception message suggests stopping with the first exception. Since there is a small cost to try: and break, I am inclined to move the try back out of the loop. The only thing it could mask is a problem with self.handlerids, and I would expect that if that is corrupted or missing, there would be problems before shutdown.

I know that in 3.4, shutdown order has been modified and the wording of the warning was discussed on pydev. I do not know (and do not care at the moment) if either ignoring __del__ exceptions or warning about them is new in 3.4. What I do remember from the discussion is agreement that 1. __del__ exceptions should be caught so shutdown can continue; 2. a warning should be given in case the exception indicates a bug in __del__; and 3. __del__ writers are responsible to catch exceptions that do not indicate a bug, so as to avoid the warning. So I am inclined to backport to all versions.
msg210361 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-02-06 04:55
More thoughts: The reason for explicit __del__ is to release memory resources connected to other objects or structures at the behest of the Python instance being deleted. If __del__ were only called at shutdown, it would not be needed, as all memory is released anyway and we could delete __del__. If __del__ is called before shutdown (as would happen if the class were unittested) then exceptions would indicate a bug and should not be ignored. From this viewpoint, not only should the try: except: be within the loop, but the break should be conditioned on the exception message.

  if exc.args[0] == 'can't invoke "bind" command:  application has been destroyed': break

The modern solution to this dilemma is to use .close instead of .__del__ and either explicitly call .close or have it called in the .__exit__ method of a context manager. For Idle classes, this would not be a problem for future test code, but it would be much trickier finding all places in current code where instances of a class with a .__del__ method get deleted. So a compromise patch with conditioned break seems most practical.

I tried a few more experiments: run the .py file (F5, to create a Shell window) and then close both windows. Whether the closing was shell first or editor first, the messages do not appear. Three runs with -n gave the same results. So it seems that the problem only appears when there has never been a shell window. I agree with Serhiy (his point 2) that this is disconcerting and could indicate a 3.4-specific problem with Tk, tkinter, or Idle.
msg210600 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-02-08 09:40
I checked that -m idlelib acts the same as -m idlelib.idle, and that there is still a problem after opening a second editor window after the original. So the messages seem tied to not opening a shell window. In testing the patch, I noticed that exceptions are now occurring in all three .__del__ methods. With the candidate release imminent, I decided to only fix what is broken and only apply to 3.4 and not worry now about possible merge problems in the future.
msg210603 - (view) Author: Roundup Robot (python-dev) Date: 2014-02-08 09:48
New changeset f9f2c57f7d00 by Terry Jan Reedy in branch 'default':
Issue #20167: Suppress 3.4 specific 'Exception ignored' messages.
http://hg.python.org/cpython/rev/f9f2c57f7d00
msg210861 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-02-10 18:48
Changes for _SimpleBinder.__del__ look doubtful. Any TclError exception is suppressed and "if" statement does nothing. Perhaps you forgot "else: raise"? And may be in other places too.
msg210872 - (view) Author: Roundup Robot (python-dev) Date: 2014-02-10 21:47
New changeset b9e124851e47 by Terry Jan Reedy in branch 'default':
Issue #20167: Add missing else: break in 3 places as noticed by Serhiy.
http://hg.python.org/cpython/rev/b9e124851e47
msg211130 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2014-02-13 06:12
Good catches, Terry and Serhiy!
msg212332 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-02-27 00:09
This has been left open to see if the undlying problem can be found. If #20567 is a guide, there might be a root.destroy that should be followed by del root.
msg228941 - (view) Author: Rusi (RusiMody) Date: 2014-10-10 02:01
Just confirming:
idle 3.4.1-1 on debian testing

Start idle3
Open recent file -> some file
Close file
Close interpreter (and idle)
Get this

Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0x7fc53638f4e0>>
Traceback (most recent call last):
  File "/usr/lib/python3.4/idlelib/MultiCall.py", line 244, in __del__
    self.widget.unbind(self.widgetinst, seq, id)
  File "/usr/lib/python3.4/tkinter/__init__.py", line 1071, in unbind
    self.tk.call('bind', self._w, sequence, '')
_tkinter.TclError: can't invoke "bind" command: application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0x7fc5363b7240>>
Traceback (most recent call last):
  File "/usr/lib/python3.4/idlelib/MultiCall.py", line 244, in __del__
    self.widget.unbind(self.widgetinst, seq, id)
  File "/usr/lib/python3.4/tkinter/__init__.py", line 1071, in unbind
    self.tk.call('bind', self._w, sequence, '')
_tkinter.TclError: can't invoke "bind" command: application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0x7fc5363b75f8>>
Traceback (most recent call last):
  File "/usr/lib/python3.4/idlelib/MultiCall.py", line 244, in __del__
    self.widget.unbind(self.widgetinst, seq, id)
  File "/usr/lib/python3.4/tkinter/__init__.py", line 1071, in unbind
    self.tk.call('bind', self._w, sequence, '')
_tkinter.TclError: can't invoke "bind" command: application has been destroyed
Exception ignored in: <bound method _ComplexBinder.__del__ of <idlelib.MultiCall._ComplexBinder object at 0x7fc5363b79b0>>
Traceback (most recent call last):
  File "/usr/lib/python3.4/idlelib/MultiCall.py", line 244, in __del__
    self.widget.unbind(self.widgetinst, seq, id)
  File "/usr/lib/python3.4/tkinter/__init__.py", line 1071, in unbind
    self.tk.call('bind', self._w, sequence, '')
_tkinter.TclError: can't invoke "bind" command: application has been destroyed
msg228943 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-10-10 02:50
The problem is the change in the exception message between these two lines
can't invoke "bind" command:  application has been destroyed
can't invoke "bind" command: application has been destroyed

In your copy of MultiCall.py, line 63-4, change
APPLICATION_GONE = '''\
can't invoke "bind" command:  application has been destroyed'''
by changing '  ' to ' '.

I will reduce the string to "application has been destroyed" and the check from equality to containment.
msg228944 - (view) Author: Roundup Robot (python-dev) Date: 2014-10-10 03:14
New changeset ce0316007b21 by Terry Jan Reedy in branch '3.4':
Issue #20167: revise condition to accomodate message change.
https://hg.python.org/cpython/rev/ce0316007b21
msg228946 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-10-10 03:16
I verified that problem had returned on Windows as well.  It would be good to have a test that would fail if the tcl error message changed again.
msg228947 - (view) Author: Rusi (RusiMody) Date: 2014-10-10 03:20
On Fri, Oct 10, 2014 at 8:46 AM, Terry J. Reedy <report@bugs.python.org> wrote:
>
> Terry J. Reedy added the comment:
>
> I verified that problem had returned on Windows as well.  It would be good to have a test that would fail if the tcl error message changed again.
>
> ----------
> resolution: fixed ->
> stage: needs patch -> test needed
> versions: +Python 3.5

It is failing again!!

Version 3.4.1-1:  checked that removing that space makes the error go away.
Then saw that there is a new version 3.4.2~rc1-1 (almost certainly not
containing your change) in the debian repos.

Upgrading to that has made the error return!
msg228948 - (view) Author: Rusi (RusiMody) Date: 2014-10-10 03:25
On Fri, Oct 10, 2014 at 8:49 AM, Rustom Mody <rustompmody@gmail.com> wrote:
> On Fri, Oct 10, 2014 at 8:46 AM, Terry J. Reedy <report@bugs.python.org> wrote:
>>
>> Terry J. Reedy added the comment:
>>
>> I verified that problem had returned on Windows as well.  It would be good to have a test that would fail if the tcl error message changed again.
>>
>> ----------
>> resolution: fixed ->
>> stage: needs patch -> test needed
>> versions: +Python 3.5
>
> It is failing again!!
>
> Version 3.4.1-1:  checked that removing that space makes the error go away.
> Then saw that there is a new version 3.4.2~rc1-1 (almost certainly not
> containing your change) in the debian repos.
>
> Upgrading to that has made the error return!

By which I meant to say not that it has 'returned'
But that its there in 3.4.2 as well
msg228952 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-10-10 05:44
I know.  3.4.2.rc1 was released over 2 weeks ago and 3.4.2 just yesterday (essentially without change).  So you have to delete space again. Since 3.4.1 worked for many people, perhaps you have an ancient version of tk.  You can get major/minor number with
>>> import tkinter as tk
>>> tk.TclVersion
8.6
but 8.5 covers a long time span and many fixes.  I forget how to get the third, micro number.
msg228953 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-10-10 06:06
> I forget how to get the third, micro number.

tkinter.Tcl().call('info', 'patchlevel')
msg228954 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-10-10 06:26
On 3.5 the issue is gone, but on 3.4 and 2.7 following traceback are generated:

$ ./python -m idlelib.idle Lib/decimal.py
Traceback (most recent call last):
  File "/home/serhiy/py/cpython-3.4/Lib/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/serhiy/py/cpython-3.4/Lib/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/serhiy/py/cpython-3.4/Lib/idlelib/idle.py", line 11, in <module>
    idlelib.PyShell.main()
  File "/home/serhiy/py/cpython-3.4/Lib/idlelib/PyShell.py", line 1562, in main
    if flist.open(filename) is None:
  File "/home/serhiy/py/cpython-3.4/Lib/idlelib/FileList.py", line 36, in open
    edit = self.EditorWindow(self, filename, key)
  File "/home/serhiy/py/cpython-3.4/Lib/idlelib/PyShell.py", line 141, in __init__
    self.color_breakpoint_text()
  File "/home/serhiy/py/cpython-3.4/Lib/idlelib/PyShell.py", line 159, in color_breakpoint_text
    self.text.tag_config('BREAK', cfg)
AttributeError: 'NoneType' object has no attribute 'tag_config'
msg228956 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-10-10 06:55
The above appears to be a system-specific open issue, not a close issue.  So it must be a different issue.

On my Win7
F:\Python\dev\4\py34\PCbuild>python_d -m idlelib ../Lib/decimal.py
<close after open with freshly built 3.4.2+>
F:\Python\dev\4\py34\PCbuild>

C:\Programs\Python34>python -m idlelib Lib/decimal.py
<ditto for installed 3.4.2
C:\Programs\Python34>

With a bad file name, I get a blank editor.

In any case, the traceback makes no sense. Before "self.color_breakpoint_text()" in __init__ are 3 "self.text.bind" statements.  So self.text = None seems implausible.
msg228957 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-10-10 07:07
> In any case, the traceback makes no sense. Before
> "self.color_breakpoint_text()" in __init__ are 3 "self.text.bind"
> statements.  So self.text = None seems implausible.

The code runs up to self.text.update() in restore_file_breaks() and runs 
further only after windows closing. At this time self.text already is None.
msg228958 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-10-10 07:14
Changing update() to update_idletasks() fixes the issue. But we should 
investigate why all works on 3.5 and is there downside of this change.
msg229106 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-10-11 22:23
Serhiy, I responded to your report and followups on a new issue, #22614.
Tal, if you can, please test ./python -m idlelib.idle Lib/decimal.py on OSX and respond there.
History
Date User Action Args
2014-10-11 22:23:03terry.reedysetstatus: open -> closed
resolution: fixed
messages: + msg229106

stage: test needed -> resolved
2014-10-10 07:14:12serhiy.storchakasetmessages: + msg228958
2014-10-10 07:07:04serhiy.storchakasetmessages: + msg228957
2014-10-10 06:55:05terry.reedysetmessages: + msg228956
2014-10-10 06:26:01serhiy.storchakasetmessages: + msg228954
versions: + Python 2.7
2014-10-10 06:06:37serhiy.storchakasetmessages: + msg228953
2014-10-10 05:44:29terry.reedysetmessages: + msg228952
2014-10-10 03:25:56RusiModysetmessages: + msg228948
2014-10-10 03:20:14RusiModysetmessages: + msg228947
2014-10-10 03:16:17terry.reedysetresolution: fixed -> (no value)
stage: needs patch -> test needed
messages: + msg228946
versions: + Python 3.5
2014-10-10 03:14:13python-devsetmessages: + msg228944
2014-10-10 02:50:39terry.reedysetmessages: + msg228943
2014-10-10 02:01:49RusiModysetnosy: + RusiMody
messages: + msg228941
2014-02-27 00:09:54terry.reedysetresolution: fixed
messages: + msg212332
stage: needs patch
2014-02-13 06:12:21taleinatsetmessages: + msg211130
2014-02-10 21:47:04python-devsetmessages: + msg210872
2014-02-10 18:48:56serhiy.storchakasetmessages: + msg210861
2014-02-08 09:48:00python-devsetnosy: + python-dev
messages: + msg210603
2014-02-08 09:40:21terry.reedysetassignee: terry.reedy
messages: + msg210600
2014-02-06 04:55:22terry.reedysetmessages: + msg210361
2014-02-05 13:38:51terry.reedysetmessages: + msg210312
2014-02-05 12:50:46taleinatsetfiles: + taleinat_idle_closing_exception_3.patch

messages: + msg210308
2014-02-05 12:44:14taleinatsetfiles: + taleinat_idle_closing_exception_2.patch

messages: + msg210307
2014-02-05 11:43:38taleinatsetmessages: + msg210304
2014-02-05 11:18:44serhiy.storchakasetmessages: + msg210300
2014-02-05 10:33:23taleinatsetfiles: + taleinat_idle_closing_exception.patch
keywords: + patch
messages: + msg210298
2014-02-05 10:24:41taleinatsetnosy: + taleinat
messages: + msg210297
2014-01-07 20:57:38serhiy.storchakacreate