GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `deque_clear` clears `MemoryError` · Issue #152017 · python/cpython

deque_clear clears MemoryError #152017

New issue
Copy link
New issue
Copy link
Open
Open
deque_clear clears MemoryError#152017
Copy link
Assignees
rhettinger
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@brijkapadia
brijkapadia
opened on Jun 23, 2026
Issue body actions

Bug report

Bug description:

This is a sub-issue for gh-146102 with the gist here.

In the case that there is no memory, deque_clear calls PyErr_Clear():

cpython/Modules/_collectionsmodule.c

Lines 749 to 753 in 7928a8b

b = newblock(deque);
if (b == NULL) {
PyErr_Clear();
goto alternate_method;
}

This was done to clear the error that newblock raises. However, if there was originally an exception, deque_clear clears it.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

  • gh-152017: Fix deque_clear to no longer alters exceptions #152018

Activity

  1. brijkapadia
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Jun 23, 2026
  2. brijkapadia
    changed the title [-]'deque_clear' clears `MemoryError`[/-] [+]`deque_clear` clears `MemoryError`[/+] on Jun 23, 2026
  3. StanFromIreland
    added
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    on Jun 23, 2026
  4. StanFromIreland
    added a parent issue on Jun 23, 2026
    • Umbrella issue: bugs found using cpython-review-toolkit #146102
  5. rhettinger commented on Jun 26, 2026

    @rhettinger
    rhettinger
    on Jun 26, 2026
    Contributor
    More actions

    What is wrong with the current code? If an allocation fails due to lack of memory (a very rare almost non-existent event), then an alternate path is taken that does not require additional memory.

    AFAICT this is a false positive from the umbrella issue.

  6. rhettinger
    self-assigned this
    on Jun 26, 2026
  7. brijkapadia commented on Jun 27, 2026

    @brijkapadia
    brijkapadia
    on Jun 27, 2026
    ContributorAuthor
    More actions

    AFAICT this is a false positive from the umbrella issue.

    There is a real error here that the reproducer catches. However, the umbrella issue gives the wrong explanation. The problem has nothing to do with deque.copy but deque.copy is one execution path for the error.

    Reproducer (for reference)

    import _testcapi
    from collections import deque
    
    for n in range(1, 80):
        d = deque(range(200))
        _testcapi.set_nomemory(n, 0)
        try:
            d2 = d.copy()
            _testcapi.remove_mem_hooks()
            break
        except MemoryError:
            _testcapi.remove_mem_hooks()
        except:
            _testcapi.remove_mem_hooks()
    # Fatal: _Py_Dealloc: Deallocator of type 'collections.deque' cleared

    The real problem is that this debug check in _Py_Dealloc fails:

    Debug Check

    cpython/Objects/object.c

    Lines 3321 to 3342 in 1812162

    #ifdef Py_DEBUG
    // gh-89373: The tp_dealloc function must leave the current exception
    // unchanged.
    if (tstate != NULL && tstate->current_exception != old_exc) {
    const char *err;
    if (old_exc == NULL) {
    err = "Deallocator of type '%s' raised an exception";
    }
    else if (tstate->current_exception == NULL) {
    err = "Deallocator of type '%s' cleared the current exception";
    }
    else {
    // It can happen if dealloc() normalized the current exception.
    // A deallocator function must not change the current exception,
    // not even normalize it.
    err = "Deallocator of type '%s' overrode the current exception";
    }
    _Py_FatalErrorFormat(__func__, err, type->tp_name);
    }
    Py_XDECREF(old_exc);
    Py_DECREF(type);
    #endif


    For the specific case of the reproducer, deque_copy_impl calls deque_append_lock_held and in the case of an error, calls Py_DECREF(new_deque):

    cpython/Modules/_collectionsmodule.c

    Lines 634 to 638 in 1812162

    if (deque_append_lock_held(new_deque, Py_NewRef(item),
    new_deque->maxlen) < 0) {
    Py_DECREF(new_deque);
    return NULL;
    }

    Furthermore, deque_append_lock_held calls newblock which raises a PyErr_NoMemory() in the case of OOM:

    cpython/Modules/_collectionsmodule.c

    Line 187 in 1812162

    PyErr_NoMemory();

    Thus, in the case of OOM, Py_DECREF(new_deque) is called with an exception already set.

    In order to free the memory, eventually deque_clear is called. deque_clear calls newblock, which in the case of OOM, replaces the current exception with a new PyErr_NoMemory(). Then, deque_clear calls PyErr_Clear(), meaning that there are now no exceptions despite deque_append_lock_held originally setting one:

    cpython/Modules/_collectionsmodule.c

    Lines 749 to 753 in 1812162

    b = newblock(deque);
    if (b == NULL) {
    PyErr_Clear();
    goto alternate_method;
    }

    In debug builds, this is caught in _Py_Dealloc,

    cpython/Objects/object.c

    Lines 3329 to 3331 in 1812162

    else if (tstate->current_exception == NULL) {
    err = "Deallocator of type '%s' cleared the current exception";
    }

    which gives the error Fatal: _Py_Dealloc: Deallocator of type 'collections.deque' cleared the current exception.

  8. stestagg commented on Jul 2, 2026

    @stestagg
    stestagg
    on Jul 2, 2026
    Contributor
    More actions

    EDIT: ignore my previous, it was something else.

    I just came across this in fuzzing, the following code reliably aborts for me:

    import collections
    d =33333333333* collections.deque([10])

    gives (in linux with 2gb ram)

    Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
    ~ # /pfm/tools/mem_limit_exec /pfm/py/bin/python3 /pfm/scratch/reproducers/t1-1.
    min.py
    Fatal Python error: _Py_Dealloc: Deallocator of type 'collections.deque' cleared the current exception
    Python runtime state: initialized
    
    Current thread 0x0000fffff7fee020 [python3] (most recent call first):
      File "<input-53>", line 2 in <module>
      File "/pfm/scratch/reproducers/t1-1.min.py", line 10 in <module>
    Aborted
    ~ # /pfm/tools/mem_limit_exec /pfm/py/bin/python3 /pfm/scratch/reproducers/t1-1.
    min.py
    Fatal Python error: _Py_Dealloc: Deallocator of type 'collections.deque' cleared the current exception
    Python runtime state: initialized
    
    Current thread 0x0000fffff7fee020 [python3] (most recent call first):
      File "<input-53>", line 2 in <module>
      File "/pfm/scratch/reproducers/t1-1.min.py", line 10 in <module>
    Aborted
    ~ #
    Python 3.16.0a0 (heads/main-dirty:f8514dc, Jul  2 2026, 09:14:40) [Clang 22.1.6 ]
    
  9. rhettinger commented on Oct 26, 2026

    @rhettinger
    rhettinger
    on Oct 26, 2026
    Contributor
    More actions

    Do you have a plain Python reproducer that doesn't rely on test_capi? Does this ever happen in real code?

  10. brijkapadia commented on Oct 28, 2026

    @brijkapadia
    brijkapadia
    on Oct 28, 2026
    ContributorAuthor
    More actions

    Do you have a plain Python reproducer that doesn't rely on test_capi? Does this ever happen in real code?

    No. There is no doubt that this is an edge case that usually does not occur. However, I think #152018 gives a relatively simple solution to this problem so its probably worth resolving (IMO).

  11. stestagg commented on Oct 28, 2026

    @stestagg
    stestagg
    on Oct 28, 2026
    Contributor
    More actions

    As per my comment above, this is easy to reproduce in memory-limited situations without needing test_capi, using this code:

    granted, it's a memory exhaustion situation, but this error is triggered.

    import collections
    d = 33333333333 * collections.deque([10]) 
    

    As an example, the following script reliably works for me on Linux (macos rlimit stuff, IIRC doesn't work)

    import collections, platform, resource, sys
    print(platform.platform(), sys.version)
    resource.setrlimit(resource.RLIMIT_AS, (2**31, 2**31))
    33333333333 * collections.deque([10])
    
    [sstagg@xps scratch]$ python test_dq_152017.py 
    Linux-7.2.7-arch1-1-x86_64-with-glibc2.44 3.14.2 (main, Feb  3 2026, 12:51:13) [GCC 15.2.1 20251112]
    Traceback (most recent call last):
      File "/home/sstagg/src/scratch/test_dq_152017.py", line 4, in <module>
        33333333333 * collections.deque([10])
        ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
    SystemError: error return without exception set
    

    AND in a debug build:

    [sstagg@xps cpython]$ ./python ~/src/scratch/test_dq_152017.py 
    Linux-7.2.7-arch1-1-x86_64-with-glibc2.44 3.16.0a1+dev (heads/main:e7ab898188c, Oct 28 2026, 08:35:51) [GCC 16.2.1 20260810]
    Fatal Python error: py_dealloc: Deallocator of type 'collections.deque' cleared the current exception
    Python runtime state: initialized
    
    Current thread 0x00007f02d7b16780 [python] (most recent call first):
      File "/home/sstagg/src/scratch/test_dq_152017.py", line 4 in <module>
    Aborted                    (core dumped) ./python ~/src/scratch/test_dq_152017.py
    
  12. stestagg commented on Oct 28, 2026

    @stestagg
    stestagg
    on Oct 28, 2026
    Contributor
    More actions

    For a real-world impact that isn't just a 'ran out of memory, program terminated' issue, here's a reproducer that, for me, silently gives invalid outputs if this error is triggered:

    import os
    import resource
    from collections import deque
    from itertools import accumulate
    
    N = 2_000_000
    chunks = (deque([None]) * N, deque([None]) * N)
    
    expected_length = sum(map(len, chunks))  # 4,000,000
    
    
    ## Set the resource limit to force a MemoryError reliaby..
    page_size = os.sysconf("SC_PAGE_SIZE")
    with open("/proc/self/statm", encoding="ascii") as f:
        current_vsize = int(f.read().split()[0]) * page_size
    
    limit = current_vsize + 8 * 1024 * 1024
    resource.setrlimit(resource.RLIMIT_AS, (limit, limit))
    ###
    
    last_result = None
    results_produced = 0
    
    for last_result in accumulate(chunks):
        results_produced += 1
    
    actual_length = len(last_result)
    
    print(f"Expected final length: {expected_length:,}")
    print(f"Actual final length:   {actual_length:,}")
    print(f"Expected results:      {len(chunks)}")
    print(f"Actual results:        {results_produced}")
    
    sstagg@xps scratch]$ python test_dq2_152017.py 
    Expected final length: 4,000,000
    Actual final length:   2,000,000
    Expected results:      2
    Actual results:        1
    

    This script was made for me by daybreak blue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @rhettinger
    rhettinger

Labels

extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.