Skip to content

Commit b71639f

Browse files
committed
Test that client context stays open on arrow streams
1 parent 6f9841d commit b71639f

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Tests that Arrow streams remain valid after their originating connection is destroyed.
2+
3+
The Arrow PyCapsule paths produce lazy streams — schema and data are consumed
4+
later. If the stream wrapper holds only a non-owning pointer to the
5+
ClientContext and the connection is GC'd in between, the pointer dangles and we
6+
crash (mutex-lock-on-destroyed-object).
7+
8+
Each test creates a capsule from a short-lived connection, destroys that
9+
connection, then consumes the capsule from a *different* connection.
10+
"""
11+
12+
import gc
13+
14+
import pytest
15+
16+
import duckdb
17+
18+
pa = pytest.importorskip("pyarrow")
19+
20+
EXPECTED = [(i, i + 1, -i) for i in range(100)]
21+
SQL = "SELECT i, i + 1 AS j, -i AS k FROM range(100) t(i)"
22+
23+
24+
class TestArrowConnectionLifetime:
25+
"""Capsules must stay valid after the originating connection is destroyed."""
26+
27+
def test_capsule_fast_path_survives_connection_gc(self):
28+
"""__arrow_c_stream__ fast path (ArrowQueryResult): connection destroyed before capsule is consumed."""
29+
conn = duckdb.connect()
30+
capsule = conn.sql(SQL).__arrow_c_stream__() # noqa: F841
31+
del conn
32+
gc.collect()
33+
result = duckdb.connect().sql("SELECT * FROM capsule").fetchall()
34+
assert result == EXPECTED
35+
36+
def test_capsule_slow_path_survives_connection_gc(self):
37+
"""__arrow_c_stream__ slow path (MaterializedQueryResult): connection destroyed before capsule is consumed."""
38+
conn = duckdb.connect()
39+
rel = conn.sql(SQL)
40+
rel.execute() # forces MaterializedQueryResult, not ArrowQueryResult
41+
capsule = rel.__arrow_c_stream__() # noqa: F841
42+
del rel, conn
43+
gc.collect()
44+
result = duckdb.connect().sql("SELECT * FROM capsule").fetchall()
45+
assert result == EXPECTED

0 commit comments

Comments
 (0)