Forum

Author Topic: 2.3.1 Python API: notimplemented_dealloc crash on Python < 3.12  (Read 2253 times)

Frédéric Devernay

  • Newbie
  • *
  • Posts: 9
    • View Profile
Environment
  • Metashape Professional 2.3.1 (build 22580), standalone Python module
  • Linux x86_64, wheel metashape-2.3.1-cp39.cp310.cp311.cp312.cp313-abi3-linux_x86_64.whl
  • CPython 3.11 (see "CPython version dependency" below — 3.12+ masks it)

Summary
Comparing None (or any non-Metashape object) with a Metashape object using == / != — for example None == chunk.cameras[0].group — returns the correct boolean but leaks a decref on the NotImplemented singleton. Each such comparison lowers NotImplemented's reference count by one; once it reaches zero the interpreter aborts:
Code: [Select]
Fatal Python error: notimplemented_dealloc: deallocating NotImplemented(SIGABRT, process exit 134.)

Minimal reproduction
Run in a Metashape 2.3.1 environment on CPython < 3.12 (e.g. 3.11) — on CPython >= 3.12 the crash is masked (see "CPython version dependency" below):
Code: [Select]
import sys
import Metashape

g = Metashape.Document().addChunk().addCameraGroup()
print("Metashape", Metashape.version, "| Python", sys.version.split()[0])
print("NotImplemented refcount:", sys.getrefcount(NotImplemented))
for i in range(1, 21):
    _ = None == g  # returns False, but leaks one reference on NotImplemented
    print(f"after {i}: NotImplemented refcount = {sys.getrefcount(NotImplemented)}")
On CPython 3.11 the printed refcount decreases by one per iteration and the process aborts with notimplemented_dealloc when it reaches zero (typically within ~5 iterations). The comparison result itself is correct (False) — the crash comes purely from the refcount corruption.

Example output (3.11):
Code: [Select]
Metashape 2.3.1.22580 | Python 3.11.15
NotImplemented refcount: 5
after 1: NotImplemented refcount = 4
after 2: NotImplemented refcount = 3
after 3: NotImplemented refcount = 2
Fatal Python error: notimplemented_dealloc: deallocating NotImplemented

Expected vs. actual
  • Expected: None == <Metashape object> returns False and leaves NotImplemented's refcount unchanged (the rich-comparison protocol borrows the reference to NotImplemented; it must not be stolen).
  • Actual: the refcount is decremented on every such comparison, eventually deallocating the singleton and aborting the interpreter.

Root cause (analysis)
None == g evaluates None.__eq__(g), which returns NotImplemented; CPython then tries the reflected g.__eq__(None). The Metashape C-extension's reflected comparison appears to over-decref the NotImplemented it handles (treating a borrowed reference as owned). It reproduces with any Metashape object type compared against None (tested with CameraGroup; the same pattern applies to markers, sensors, components, cameras, etc.). The module also over-decrefs None itself in some comparison paths (same failure mode as none_dealloc).

CPython version dependency
  • CPython < 3.12NotImplemented is an ordinary singleton with a small refcount, so the leak reaches zero and the interpreter aborts. A single comparison only makes it off-by-one, so the abort is deferred and can surface at a much later, unrelated point in the program.
  • CPython >= 3.12 (immortal objects, PEP 683) — NotImplemented is immortal (sys.getrefcount(NotImplemented) reports 4294967295), so incref/decref are no-ops and the leak is masked (no crash). The underlying refcount bug is still present, just harmless.

Impact
On CPython < 3.12, any code that compares a Metashape object with == / != where an operand is None will intermittently abort. This is easy to hit unintentionally — e.g. iterating cameras and testing camera.group == some_group while some cameras are ungrouped (camera.group is None), which produces None == some_group.

Workarounds

On CPython 3.11 (where the crash occurs)
  • Avoid == / != on Metashape objects in your own code. Use is / is not for None checks and compare entities by their integer .key:
    # instead of:  if camera.group == group:
    if camera.group is not None and camera.group.key == group.key:

    Removes the leak triggers in code you control (not any comparison inside the module itself).
  • Re-add the dropped reference via ctypes right after a known-leaking comparison (brittle; only if you can pinpoint every call):
    import ctypes
    _ = None == some_metashape_obj
    ctypes.pythonapi.Py_IncRef(ctypes.py_object(NotImplemented))
  • Immortalize the singletons at startup (last resort) — mimic CPython >= 3.12 by inflating the refcounts once so the leak can never reach zero:
    import ctypes
    for obj in (None, NotImplemented):
        ctypes.c_ssize_t.from_address(id(obj)).value += 1 << 30

    Effective but pokes at CPython internals and only masks the bug — use with care.

Recommended: run on CPython >= 3.12
On CPython >= 3.12, None and NotImplemented are immortal (PEP 683), so the leaked decrefs are no-ops and no code changes are needed.

The real fix is for the Metashape C-extension to stop stealing the borrowed NotImplemented (and None) reference in its comparison paths.
« Last Edit: July 09, 2026, 05:01:17 PM by Frédéric Devernay »

Alexey Pasumansky

  • Agisoft Technical Support
  • Hero Member
  • *****
  • Posts: 15726
    • View Profile
Re: 2.3.1 Python API: notimplemented_dealloc crash on Python < 3.12
« Reply #1 on: July 27, 2026, 01:28:23 PM »
Hello Frédéric,

Thank you for reporting the problem with Python. We have fixed it in Metashape 2.3.2 build 22776 pre-release version:

https://download.agisoft.com/metashape-pro_2_3_2_x64.msi
https://download.agisoft.com/metashape-pro_2_3_2.dmg
https://download.agisoft.com/metashape-pro_2_3_2_amd64.tar.gz

Best regards,
Alexey Pasumansky,
Agisoft LLC

Frédéric Devernay

  • Newbie
  • *
  • Posts: 9
    • View Profile
Re: 2.3.1 Python API: notimplemented_dealloc crash on Python < 3.12
« Reply #2 on: July 27, 2026, 06:10:40 PM »
Thank you!
Do you also have pre-release builds of the Python 3 module?


Frédéric Devernay

  • Newbie
  • *
  • Posts: 9
    • View Profile
Re: 2.3.1 Python API: notimplemented_dealloc crash on Python < 3.12
« Reply #4 on: August 24, 2026, 04:04:07 PM »
Fix confirmed on 2.3.2.22956, thanks!