Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeyError at exit after 'import threading' in other thread #44231

Closed
cwalther mannequin opened this issue Nov 14, 2006 · 28 comments
Closed

KeyError at exit after 'import threading' in other thread #44231

cwalther mannequin opened this issue Nov 14, 2006 · 28 comments
Labels
3.9 only security fixes 3.10 only security fixes 3.11 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@cwalther
Copy link
Mannequin

cwalther mannequin commented Nov 14, 2006

BPO 1596321
Nosy @gpshead, @amauryfa, @pitrou, @vstinner, @briancurtin, @ericsnowcurrently, @miss-islington, @iritkatriel
PRs
  • bpo-1596321: Fix threading._shutdown() for the main thread #28549
  • [3.10] bpo-1596321: Fix threading._shutdown() for the main thread (GH-28549) #28588
  • [3.9] bpo-1596321: Fix threading._shutdown() for the main thread (GH-28549) #28589
  • Files
  • threadingbug.py: minimal reproducing example
  • thread_crash.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2021-09-30.01:41:09.993>
    created_at = <Date 2006-11-14.14:02:45.000>
    labels = ['interpreter-core', 'type-bug', '3.9', '3.10', '3.11']
    title = "KeyError at exit after 'import threading' in other thread"
    updated_at = <Date 2021-09-30.01:41:09.992>
    user = 'https://bugs.python.org/cwalther'

    bugs.python.org fields:

    activity = <Date 2021-09-30.01:41:09.992>
    actor = 'vstinner'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-09-30.01:41:09.993>
    closer = 'vstinner'
    components = ['Interpreter Core']
    creation = <Date 2006-11-14.14:02:45.000>
    creator = 'cwalther'
    dependencies = []
    files = ['2213', '9195']
    hgrepos = []
    issue_num = 1596321
    keywords = ['patch']
    message_count = 26.0
    messages = ['30544', '30545', '30546', '30547', '60013', '60035', '60041', '60050', '60052', '60083', '97989', '98031', '101890', '107903', '107904', '109088', '110202', '133043', '194597', '358743', '399693', '402573', '402740', '402744', '402745', '402746']
    nosy_count = 16.0
    nosy_names = ['gregory.p.smith', 'guettli', 'amaury.forgeotdarc', 'Rhamphoryncus', 'pitrou', 'bronger', 'vstinner', 'cwalther', 'brian.curtin', 'cmcqueen1975', 'alexis', 'Laurent.Mazuel', 'eric.snow', 'hasenpfeffer', 'miss-islington', 'iritkatriel']
    pr_nums = ['28549', '28588', '28589']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue1596321'
    versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']

    @cwalther
    Copy link
    Mannequin Author

    cwalther mannequin commented Nov 14, 2006

    Python 2.4.3 on Windows 2000, though the code in
    question seems unchanged in current SVN (r46919).

    I'm using Python embedded in a multithreaded C++
    application. When 'import threading' is first done in
    some Python script that runs in thread A, I get the
    following exception when a different thread B calls
    Py_Finalize():

    Error in atexit._run_exitfuncs:
    Traceback (most recent call last):
      File "C:\Python24\lib\atexit.py", line 24, in
    _run_exitfuncs
        func(*targs, **kargs)
      File "C:\Python24\lib\threading.py", line 636, in
    __exitfunc
        self._Thread__delete()
      File "C:\Python24\lib\threading.py", line 522, in
    __delete
        del _active[_get_ident()]
    KeyError: 680
    Error in sys.exitfunc:
    Traceback (most recent call last):
      File "C:\Python24\lib\atexit.py", line 24, in
    _run_exitfuncs
        func(*targs, **kargs)
      File "C:\Python24\lib\threading.py", line 636, in
    __exitfunc
        self._Thread__delete()
      File "C:\Python24\lib\threading.py", line 522, in
    __delete
        del _active[_get_ident()]
    KeyError: 680

    The reason seems to be that the threading module uses
    the thread ID of the calling thread as a key to store
    its _MainThread instance on initialization, and again
    the thread ID of the calling thread to delete it in its
    exit function. If these two threads are not the same,
    the described KeyError occurs.

    I didn't study this in all detail, but it seems to me
    that threading.Thread.__delete() does the wrong thing.
    By doing 'del _active[_get_ident()]', it removes the
    instance for the calling thread from the _active
    dictionary. What it should be doing is removing *self*
    from that dictionary. Is that correct?

    @cwalther cwalther mannequin added interpreter-core (Objects, Python, Grammar, and Parser dirs) labels Nov 14, 2006
    @brettcannon
    Copy link
    Member

    Well, I don't think you should be calling Py_Finalize() from the non-main thread. That just seems unsafe to me.

    Regardless, though, could you write up some quick Python code that triggers this?

    @cwalther
    Copy link
    Mannequin Author

    cwalther mannequin commented Nov 15, 2006

    I'm not calling Py_Finalize from a non-main thread. What I called "thread B" is the main thread. It's the script that first imports the threading module that runs in a non-main thread (and running user-defined scripts in non-main threads is hopefully not unsafe, or there wouldn't be much point in supporting multithreading at all in Python).

    It didn't even occur to me that this could be reproduced in pure Python code, so I didn't include an example in my original post. Of course, it can - see attachment. Tested on Python 2.3.5 on Mac OS X.

    @brettcannon
    Copy link
    Member

    Thanks for the test code. I have no clue when I or anyone else will get to this, but the report and testing code is appreciated.

    @gpshead
    Copy link
    Member

    gpshead commented Jan 17, 2008

    threadingbug.py doesn't fail for me on trunk (linux), anyone else?

    the output I get is always:

    Main thread ID: -134346528
    Secondary thread ID: -135349328
    Exception KeyError: KeyError(-134346528,) in <module 'threading' from
    '/home/gps/oss/python/trunk/Lib/threading.pyc'> ignored

    @cwalther
    Copy link
    Mannequin Author

    cwalther mannequin commented Jan 17, 2008

    I'm not sure what you mean by "doesn't fail" - from the output you quote,
    I'd say that it does fail. It's in fact the same output as I get right now
    with Python 2.5.1 on Mac OS X.

    Would you classify that KeyError as expected behavior?

    @gpshead
    Copy link
    Member

    gpshead commented Jan 17, 2008

    gah, sorry i misread the report. you are correct.

    @Rhamphoryncus
    Copy link
    Mannequin

    Rhamphoryncus mannequin commented Jan 17, 2008

    Is the bug avoided if you import threading first and use it instead of
    thread? I'd like to see thread removed in 3.0 (renamed to _thread or
    the like.)

    @amauryfa
    Copy link
    Member

    If a python daemon thread is still running when the interpreter exits,
    it is likely to fail in random ways.

    Here is another example, which does not use imports.
    I run the script many times, with latest version in trunk, on Windows
    XP, debug build.

    In the majority of runs, I get an error message:
    """
    Exception in thread Thread-1 (most likely raised during interpreter
    shutdown):
    """
    Other tests seem to show that all modules are emptied by the cleanup
    process, but the thread insists to get "time.sleep".

    And more interestingly, about every 50 runs, the process segfaults...
    I suspect that this is a problem similar to http://bugs.python.org/issue1856

    @cwalther
    Copy link
    Mannequin Author

    cwalther mannequin commented Jan 18, 2008

    Is the bug avoided if you import threading first and use it instead of thread?

    Yes. The bug happens when the (first) import of threading and the call to Py_Finalize()
    happen in different threads. To reproduce the problem in pure Python, I therefore have to
    use thread instead of threading to create the secondary thread. (In the C++ application,
    it's created on the C++ side.)

    Has anyone checked if the solution I propose in the first post makes sense?

    @briancurtin
    Copy link
    Member

    FWIW, Amaury's example runs without error on trunk and py3k (OS X 10.5).

    2.6 prints the following:
    "Exception in thread Thread-1 (most likely raised during interpreter shutdown)"

    3.1 seg faults

    @briancurtin briancurtin added type-bug An unexpected behavior, bug, or error labels Jan 18, 2010
    @cwalther
    Copy link
    Mannequin Author

    cwalther mannequin commented Jan 18, 2010

    I have the impression we're tracking two completely unrelated problems in this tracker item.

    As to "needs patch" regarding my problem: Here's the solution I proposed in my original post in patch form - I'm just not sure if it is correct. I don't recommend applying this until someone who is familiar with the workings of the threading module has confirmed that removing self from _active is the right thing to do (and that what I'm doing is the accepted pythonic way of removing a dictionary entry by value).

    Index: Lib/threading.py
    ===================================================================

    --- Lib/threading.py    (revision 77598)
    +++ Lib/threading.py    (working copy)
    @@ -611,7 +611,11 @@
     
             try:
                 with _active_limbo_lock:
    -                del _active[_get_ident()]
    +                for k, v in _active.iteritems():
    +                    if v is self: break
    +                else:
    +                    assert False, "thread instance not found in _active"
    +                del _active[k]
                     # There must not be any python code between the previous line
                     # and after the lock is released.  Otherwise a tracing function
                     # could try to acquire the lock again in the same thread, (in

    @pitrou
    Copy link
    Member

    pitrou commented Mar 29, 2010

    I think the fix to Christian's issue is just:

    Index: Lib/threading.py
    ===================================================================

    --- Lib/threading.py	(révision 79470)
    +++ Lib/threading.py	(copie de travail)
    @@ -579,7 +579,7 @@
                     try:
                         # We don't call self.__delete() because it also
                         # grabs _active_limbo_lock.
    -                    del _active[_get_ident()]
    +                    del _active[self.__ident]
                     except:
                         pass
     
    @@ -615,7 +615,7 @@
     
             try:
                 with _active_limbo_lock:
    -                del _active[_get_ident()]
    +                del _active[self.__ident]
                     # There must not be any python code between the previous line
                     # and after the lock is released.  Otherwise a tracing function
                     # could try to acquire the lock again in the same thread, (in

    Now we just need to add a test for it in test_threading.
    And, yes, Amaury's test case looks like a different issue.

    @cmcqueen1975
    Copy link
    Mannequin

    cmcqueen1975 mannequin commented Jun 16, 2010

    From my limited experience using cx_Freeze 4.1.2 with Python 2.6.5, it seems that this issue is triggered in a cx_Frozen program simply by having import threading in the program. I'm not sure what cx_Freeze is doing that makes this issue show up.

    @cmcqueen1975
    Copy link
    Mannequin

    cmcqueen1975 mannequin commented Jun 16, 2010

    Sorry I should have said, I'm running on Windows 2000 SP4.

    @cmcqueen1975
    Copy link
    Mannequin

    cmcqueen1975 mannequin commented Jul 2, 2010

    A follow-on re the cx_Freeze issue: I looked at the source code, and found it doesn't seem to be doing any thread creation. But I found that in the initscripts/Console.py, there are the following lines:

        if sys.version_info[:2] >= (2, 5):
            module = sys.modules.get("threading")
            if module is not None:
                module._shutdown()

    If these lines are commented-out, then the error message at exit does not occur.

    @LaurentMazuel
    Copy link
    Mannequin

    LaurentMazuel mannequin commented Jul 13, 2010

    Another solution for cx-freeze problem:
    http://code.google.com/p/modwsgi/issues/detail?id=197#c5

    Which can be added in ConsoleKeepPath.c for instance

    @hasenpfeffer
    Copy link
    Mannequin

    hasenpfeffer mannequin commented Apr 5, 2011

    I encountered this issue recently in Python 3.2 and wanted to make some observations about it.

    The real problem here is not the KeyError. Though the suggested patches would fix the KeyError symptom, they do not fix the underlying issue. The underlying issue is the threading module assumes that it is imported from the Python main thread. When alien threads exist, the threading module may be imported (directly or indirectly) from a thread that is not the Python main thread, causing the wrong thread to be marked as the Python main thread.

    The resulting problems of the wrong thread being marked as the Python main thread appear to be minor. The KeyError at exit is one of them. Another problem I encountered was with confusion in a threaded debugger that displayed my alien thread as the Python main thread, and the Python main thread as the alien thread.

    In my project I can easily work around this by importing the threading module in a root package that is extremely likely to be imported from the Python main thread, causing the correct thread to be marked as the main thread.

    Since I have a workaround for my project, in addition to the relatively minor issues that result from this behavior, I haven't spent any time looking into how to fix the underlying issue. If I have the time, I'll suggest one.

    @guettli
    Copy link
    Mannequin

    guettli mannequin commented Aug 7, 2013

    Only few people seem to use daemon threads. We do and see this problem often with Python 2.7.

    How difficult is it to get this fixed for 2.7?

    Is there a way to work around this problem?

    @ericsnowcurrently
    Copy link
    Member

    related: issue bpo-39042 "Use the runtime's main thread ID in the threading module."

    @iritkatriel
    Copy link
    Member

    The output is different now.

    I update the script for python 3:

    ---------------------------------------------

    import _thread as thread
    import time
    
    def start():
    	print("Secondary thread ID:", thread.get_ident())
    	import threading
    
    print("Main thread ID:", thread.get_ident())
    thread.start_new_thread(start, ())
    time.sleep(1)

    and the output is:

    ---------------------------------------------

    Main thread ID: 4432801280
    Secondary thread ID: 123145384259584
    Exception ignored in: <module 'threading' from '/Users/iritkatriel/src/cpython-perf/Lib/threading.py'>
    Traceback (most recent call last):
      File "/Users/iritkatriel/src/cpython-perf/Lib/threading.py", line 1512, in _shutdown
        assert tlock.locked()
        ^^^^^^^^^^^^^^^^^^^^^
    AssertionError:

    @vstinner
    Copy link
    Member

    I proposed PR 28549 to fix this very old threading issue.

    A C extension can spawn threads without using the threading module and then then run Python code which imports the threading module. In this case, threading._main_thread is the thread which imported first the threading module. My PR changes so threading._shutdown() simply ignores _main_thread when it happens.

    @vstinner
    Copy link
    Member

    New changeset 95d3137 by Victor Stinner in branch 'main':
    bpo-1596321: Fix threading._shutdown() for the main thread (GH-28549)
    95d3137

    @miss-islington
    Copy link
    Contributor

    New changeset 38c6773 by Miss Islington (bot) in branch '3.10':
    bpo-1596321: Fix threading._shutdown() for the main thread (GH-28549)
    38c6773

    @vstinner
    Copy link
    Member

    New changeset 94d19f6 by Victor Stinner in branch '3.9':
    bpo-1596321: Fix threading._shutdown() for the main thread (GH-28549) (GH-28589)
    94d19f6

    @vstinner
    Copy link
    Member

    Better late than never. I only took 15 years to fix this old bug :-D

    @vstinner vstinner added 3.9 only security fixes 3.10 only security fixes 3.11 only security fixes labels Sep 27, 2021
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @zhuofeng6
    Copy link

    the bug occur on python 3.7?

    @iritkatriel
    Copy link
    Member

    3.7 is no longer getting bug fixes.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.9 only security fixes 3.10 only security fixes 3.11 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    10 participants