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

list(sys.modules.items()) can throw RuntimeError: dictionary changed size during iteration #84507

Closed
mmohrhard mannequin opened this issue Apr 19, 2020 · 8 comments
Closed
Labels
3.7 (EOL) end of life extension-modules C modules in the Modules dir type-bug An unexpected behavior, bug, or error

Comments

@mmohrhard
Copy link
Mannequin

mmohrhard mannequin commented Apr 19, 2020

BPO 40327
Nosy @rhettinger, @nirs, @serhiy-storchaka, @miss-islington, @mmohrhard
PRs
  • bpo-40327: Improve atomicity speed and memory efficiency of the items() loop #19628
  • [3.8] bpo-40327: Improve atomicity, speed, and memory efficiency of the items() loop (GH-19628) #19640
  • 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 2020-05-22.23:15:10.063>
    created_at = <Date 2020-04-19.08:24:06.095>
    labels = ['extension-modules', 'type-bug', '3.7']
    title = 'list(sys.modules.items()) can throw RuntimeError: dictionary changed size during iteration'
    updated_at = <Date 2021-08-26.00:30:48.133>
    user = 'https://github.com/mmohrhard'

    bugs.python.org fields:

    activity = <Date 2021-08-26.00:30:48.133>
    actor = 'Andrew Nelson'
    assignee = 'none'
    closed = True
    closed_date = <Date 2020-05-22.23:15:10.063>
    closer = 'rhettinger'
    components = ['Extension Modules']
    creation = <Date 2020-04-19.08:24:06.095>
    creator = 'Markus Mohrhard'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 40327
    keywords = ['patch']
    message_count = 8.0
    messages = ['366762', '366798', '366804', '366854', '366952', '369660', '373399', '400301']
    nosy_count = 6.0
    nosy_names = ['rhettinger', 'nirs', 'serhiy.storchaka', 'miss-islington', 'Markus Mohrhard', 'Andrew Nelson']
    pr_nums = ['19628', '19640']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue40327'
    versions = ['Python 3.7']

    @mmohrhard
    Copy link
    Mannequin Author

    mmohrhard mannequin commented Apr 19, 2020

    We have hit an issue in the pickle module where the code throws an exception in a threaded environment:

    The interesting piece of the backtrace is:

    File "/xxx/1004060/lib/python3.7/site-packages/numpy/core/init.py", line 130, in _ufunc_reduce
    return _ufunc_reconstruct, (whichmodule(func, name), name)
    File "/xxx/lib/python3.7/pickle.py", line 309, in whichmodule
    for module_name, module in list(sys.modules.items()):
    RuntimeError: dictionary changed size during iteration

    I tried to find a code path that would explain how the dict could be changed while the list is created but have not been able to find a code path that releases the GIL.

    The executable is using many threads with imports happening in random threads and a custom class loader but we already make sure that the class loader is always holding the GIL.

    The issue happens quite rarely (maybe once every one thousand's execution) so I don't have a reproducer right now.

    @mmohrhard mmohrhard mannequin added 3.7 (EOL) end of life extension-modules C modules in the Modules dir type-bug An unexpected behavior, bug, or error labels Apr 19, 2020
    @rhettinger
    Copy link
    Contributor

    Suggest making this change:

    • for module_name, module in list(sys.modules.items()):
      + for module_name, module in sys.modules.copy().items():

    @rhettinger
    Copy link
    Contributor

    We should consider making this change just about everywhere in the standard library. Besides avoiding a race condition, running a for loop over d.copy().items() is more memory efficient and possibly faster than a for loop over list(d.items()).

    @serhiy-storchaka
    Copy link
    Member

    I afraid that this is a part of the larger issue (see also bpo-31165). Seems the cause is that the GIL can be released when allocate or reallocate the memory. Does your project use some memory managing hooks?

    Using sys.modules.copy().items() can help a lot, because you allocate memory for new dict only once. In case of list(sys.modules.items()) you allocate memory for every key-value pair, and may allocate memory several times to resize a list.

    This reduces the probability of occurring the problem in many times, but not to zero. There is still a chance that the size of the original dict increase when you allocate a memory for a new dict, so copying the data to a new dict can fail or even crash or spoil the memory.

    @rhettinger
    Copy link
    Contributor

    New changeset 75bedbe by Raymond Hettinger in branch 'master':
    bpo-40327: Improve atomicity, speed, and memory efficiency of the items() loop (GH-19628)
    75bedbe

    @miss-islington
    Copy link
    Contributor

    New changeset 16d0781 by Miss Islington (bot) in branch '3.8':
    bpo-40327: Improve atomicity, speed, and memory efficiency of the items() loop (GH-19628)
    16d0781

    @nirs
    Copy link
    Mannequin

    nirs mannequin commented Jul 9, 2020

    Does this really affect only python 3.7?

    We see this in RHEL 8.2, using python 3.6.8:
    https://bugzilla.redhat.com/show_bug.cgi?id=1837199#c69

    Likely caused by:

        lvs = dict(self._lvs)

    Without locking. self._lvs is a dict that may contain 1000's of items.

    I'm not sure if this is relvant now for upstream, but backport to 3.6 would be useful.

    @AndrewNelson
    Copy link
    Mannequin

    AndrewNelson mannequin commented Aug 26, 2021

    Just to mention that we have seen similar issues whilst importing scipy in a multiprocessing context.
    See scipy/scipy#11479 and https://stackoverflow.com/questions/68911221/python-runtimeerror-dictionary-changed-size-during-iteration-popping-out-rand.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    openstack-mirroring pushed a commit to openstack/keystoneauth that referenced this issue Nov 28, 2023
    We have hit an error with
    "dictionary changed size during iteration" a few times during
    this week in telemetry integration tests. It seems like it's
    hitting only our ubuntu based jobs, I haven't seen this error
    in a centos based job yet. Example of the failed job can be
    found in [1], I extracted a traceback, which leads to keystoneauth1
    into [2]. According to [3] and [4] using copy() should help with
    the issue. The python docs [5] indicate, that copy() should always
    be used when iterating through sys.modules
    
    [1] https://zuul.opendev.org/t/openstack/build/c99db592871a441e9cddad2f4e60c2fc/console
    [2] https://paste.opendev.org/show/bpzng2EUyFh1tvBHczt7/
    [3] python/cpython#84507
    [4] python/cpython#89516
    [5] https://docs.python.org/3/library/sys.html#sys.modules
    
    Change-Id: I50500c6a21bbe60050303cea4628ca9b71a3e0eb
    openstack-mirroring pushed a commit to openstack/openstack that referenced this issue Nov 28, 2023
    * Update keystoneauth from branch 'master'
      to 3b492a7aa0bcb73c7fff62c8e7b1cd5d42118fbe
      - Fix "dictionary changed size during iteration"
        
        We have hit an error with
        "dictionary changed size during iteration" a few times during
        this week in telemetry integration tests. It seems like it's
        hitting only our ubuntu based jobs, I haven't seen this error
        in a centos based job yet. Example of the failed job can be
        found in [1], I extracted a traceback, which leads to keystoneauth1
        into [2]. According to [3] and [4] using copy() should help with
        the issue. The python docs [5] indicate, that copy() should always
        be used when iterating through sys.modules
        
        [1] https://zuul.opendev.org/t/openstack/build/c99db592871a441e9cddad2f4e60c2fc/console
        [2] https://paste.opendev.org/show/bpzng2EUyFh1tvBHczt7/
        [3] python/cpython#84507
        [4] python/cpython#89516
        [5] https://docs.python.org/3/library/sys.html#sys.modules
        
        Change-Id: I50500c6a21bbe60050303cea4628ca9b71a3e0eb
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life extension-modules C modules in the Modules dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants