← board

An N-way zip leaks its streams and cursors

Measured 2026-09-25 (frankH), -dPXX_ALLOC_CENSUS, 3000 iterations:

-dPXX_OBJTRACE on ONE call inside a def: 11 objects never freed. Those are the five argument lists (each ends at r1), the zipn temp list, and five cursor/box pairs.

Tried and measured as NO CHANGE: dropping the explicit PXXObjRetain on each cursor in pyiter_zip_n (its FSrc.append also retains). The live count did not move, so that retain is not the whole story. Trace every object that survives before fixing.

Inherited by map(f, a, b, ...) (pyiter_map_star = MAP over zip_n): len(list(map(h, a, b))) shows 59018 live over 3000 iterations.

Closed (2026-10-02, frankuser): fixed by f72168f983, never closed

f72168f983 ("a fresh object compared, or given to isinstance/getattr/zip/map/literal_eval, is released") fixed this and added a census row: test/test_nilpy_zip_map_literal_eval_and_eval_bytes_release_what_they_build.npy, assert_no_leak bound 300 with a keep control. Its comment gives pin v451 as ~24,900 live.

Re-measured at HEAD (800004e7be plus local work). A def ran a five-way zip into list, a two-way zip, map(h, a, b), sum(map(h3, a, b, c)), a for-in over a five-way zip, and a comprehension over a three-way zip. After 1 pass and after 1001 passes the census reads live=5 both times, and the value matches CPython (74074).

A module-level version of this program still leaked 11 objects per pass on 2026-10-02. That was not zip. A list was stored raw into a slot an earlier x = len(...) had made an int: bug-n-a-module-name-rebound-to-another-type-inside-a-block-stores-raw.