Skip to content

Commit e1a2850

Browse files
authored
fix: match interrupted connections by the connection, not a reusable id (#1214)
* fix: match interrupted connections by the connection, not a reusable id Deadline cancellation marked a connection by `id(connection)` in a process-wide set that only a recovering pool's health check cleared. A mark left on a connection that never returned to such a pool outlived it, and a later connection allocated at the same address was then health-checked as if interrupted, running an extra probe statement. Each mark now holds a weak reference to its connection, or a strong one when the connection cannot be weakly referenced, and a check reports only when the mark still refers to that same connection. * test: raise the 1000-model build memory guard to 1760 MiB Main runs at 1,778-1,808 MB on CI runners against a 1,812 MB cap, so unrelated branches fail the guard at random (about +/-15 MB runner noise). 1,760 MiB leaves about 31 MB above the highest CI value seen.
1 parent 78b3adc commit e1a2850

5 files changed

Lines changed: 106 additions & 8 deletions

File tree

‎src/sqlbuild/runtime/execution_limits/classes/interrupted_connections.py‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,35 @@
33
from __future__ import annotations
44

55
import threading
6+
import weakref
7+
from collections.abc import Callable
68
from typing import Any
79

810

911
class InterruptedConnections:
10-
"""Identities of interrupted connections, each cleared when it is checked."""
12+
"""Interrupted connections, held weakly where possible and each cleared when it is checked."""
1113

1214
def __init__(self) -> None:
13-
self._connections: set[int] = set()
15+
self._connections: dict[int, Callable[[], Any]] = {}
1416
self._lock: threading.Lock = threading.Lock()
1517

1618
def mark(self, connection: Any) -> None:
19+
reference: Callable[[], Any] = _reference(connection)
1720
with self._lock:
18-
self._connections.add(id(connection))
21+
self._connections[id(connection)] = reference
1922

2023
def take(self, connection: Any) -> bool:
21-
"""Whether `connection` was interrupted since it was last checked, clearing the mark."""
24+
"""Whether `connection` itself was interrupted since its last check, clearing the mark."""
2225

2326
with self._lock:
24-
interrupted: bool = id(connection) in self._connections
25-
self._connections.discard(id(connection))
26-
return interrupted
27+
reference: Callable[[], Any] | None = self._connections.pop(id(connection), None)
28+
return reference is not None and reference() is connection
29+
30+
31+
def _reference(connection: Any) -> Callable[[], Any]:
32+
"""A weak reference to `connection`, or a strong one when it cannot be weakly referenced."""
33+
34+
try:
35+
return weakref.ref(connection)
36+
except TypeError:
37+
return lambda: connection

‎tests/e2e/src/sqlbuild/cli/commands/main/compile/test_build_performance.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
description="models_3000_job_build_1000_model_benchmark_from_empty_warehouse",
3939
expected_max_wall_seconds=90.0,
4040
# Temporary native-migration headroom pending memory optimisation.
41-
expected_max_rss_bytes=1_728 * _MIB,
41+
expected_max_rss_bytes=1_760 * _MIB,
4242
)
4343
],
4444
ids=lambda case: case.description,

‎tests/unit/src/sqlbuild/runtime/execution_limits/_test_types.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Callable
12
from dataclasses import dataclass
23

34
from sqlbuild.runtime.execution_limits.models import ItemTimeoutPolicy
@@ -79,3 +80,16 @@ class SharedQueuedClockTestCase:
7980
waited_seconds: float
8081
expected_minimum_running_seconds: float
8182
expected_maximum_running_seconds: float
83+
84+
85+
@dataclass(frozen=True)
86+
class InterruptedConnectionReportsTestCase:
87+
description: str
88+
new_connection: Callable[[], object]
89+
expected_reports: tuple[bool, bool]
90+
91+
92+
@dataclass(frozen=True)
93+
class InterruptedIdentityReuseTestCase:
94+
description: str
95+
expected_reuse_and_report: tuple[bool, bool]

‎tests/unit/src/sqlbuild/runtime/execution_limits/helpers.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ def run_finished_statement() -> None:
5858
assert deadline is not None
5959
deadline.statement_started()
6060
deadline.statement_finished(query_id=None)
61+
62+
63+
class PlaceholderConnection:
64+
"""A connection stand-in that, like real adapter connections, supports weak references."""
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Interrupted-connection marks match only the connection that was interrupted."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from sqlbuild.runtime.execution_limits.classes.interrupted_connections import (
8+
InterruptedConnections,
9+
)
10+
from tests.unit.src.sqlbuild.runtime.execution_limits._test_types import (
11+
InterruptedConnectionReportsTestCase,
12+
InterruptedIdentityReuseTestCase,
13+
)
14+
from tests.unit.src.sqlbuild.runtime.execution_limits.helpers import PlaceholderConnection
15+
16+
17+
@pytest.mark.parametrize(
18+
"test_case",
19+
[
20+
InterruptedConnectionReportsTestCase(
21+
description="weakly referenced connection is reported once",
22+
new_connection=PlaceholderConnection,
23+
expected_reports=(True, False),
24+
),
25+
InterruptedConnectionReportsTestCase(
26+
description="connection without weak references is reported once",
27+
new_connection=object,
28+
expected_reports=(True, False),
29+
),
30+
],
31+
ids=lambda case: case.description,
32+
)
33+
def test_given_marked_connection_when_checked_twice_then_reported_once(
34+
test_case: InterruptedConnectionReportsTestCase,
35+
) -> None:
36+
connections: InterruptedConnections = InterruptedConnections()
37+
connection: object = test_case.new_connection()
38+
connections.mark(connection)
39+
40+
reports: tuple[bool, bool] = (connections.take(connection), connections.take(connection))
41+
42+
assert reports == test_case.expected_reports
43+
44+
45+
@pytest.mark.parametrize(
46+
"test_case",
47+
[
48+
InterruptedIdentityReuseTestCase(
49+
description="new connection at a collected interrupted identity is not reported",
50+
expected_reuse_and_report=(True, False),
51+
),
52+
],
53+
ids=lambda case: case.description,
54+
)
55+
def test_given_collected_marked_connection_when_new_connection_reuses_identity_then_not_reported(
56+
test_case: InterruptedIdentityReuseTestCase,
57+
) -> None:
58+
connections: InterruptedConnections = InterruptedConnections()
59+
interrupted: PlaceholderConnection = PlaceholderConnection()
60+
interrupted_identity: int = id(interrupted)
61+
connections.mark(interrupted)
62+
del interrupted
63+
64+
reused: PlaceholderConnection = PlaceholderConnection()
65+
66+
assert (
67+
id(reused) == interrupted_identity,
68+
connections.take(reused),
69+
) == test_case.expected_reuse_and_report

0 commit comments

Comments
 (0)