◐ Shell
clean mode source ↗

fix(serializer): avoid creating reference cycles on every call by Malkiz223 · Pull Request #6563 · getsentry/sentry-python

Description

serialize() used to define eight nested functions referencing each other through closure cells, so every call left a reference cycle behind. Only the cyclic GC can free such cycles, so applications running with gc.disable() leak memory on every captured event, and everyone else pays collector pressure on the error path. On Python 3.14 the cycle is even bigger: PEP 649 adds a hidden __annotate__ function per nested function, and those get caught in it too.

As discussed in #6559: the helpers are moved as-is onto a private per-call class _Serializer, and serialize() creates an instance of it. The public serialize() and its signature are unchanged, the helper logic is untouched, so behaviour stays identical. Everything a call creates is now freed by plain reference counting.

There is no performance cost. In an interleaved microbenchmark (medians of 9 rounds, small/medium/frame-vars sample events) the class version performs the same as the nested functions on Python 3.12. On Python 3.14 small events serialize ~1.5x faster (9.0 -> 5.7 us/call here), since a call no longer creates eight closures plus their PEP 649 __annotate__ functions.

Also adds a regression test asserting that serialize() leaves no cyclic garbage behind (checked under gc.DEBUG_SAVEALL with the GC disabled).

Issues