|
17 | 17 |
|
18 | 18 | import concurrent.futures |
19 | 19 | import json |
| 20 | +import logging |
20 | 21 | import math |
21 | 22 | import re |
22 | 23 | from typing import Any, Optional |
23 | 24 |
|
| 25 | +logger = logging.getLogger(__name__) |
| 26 | + |
24 | 27 | WGS84 = "EPSG:4326" |
25 | 28 |
|
26 | 29 | # Wall-clock budget for a single SQL statement (execute + materialise). Mirrors |
@@ -140,6 +143,7 @@ def run_sql(sql: str, layers: Optional[list[dict]] = None) -> dict: |
140 | 143 | import geopandas as gpd # noqa: PLC0415 |
141 | 144 |
|
142 | 145 | connection = sedona_db.connect() |
| 146 | + future = None |
143 | 147 | try: |
144 | 148 | for layer in layers or []: |
145 | 149 | name = str(layer.get("name") or "").strip() |
@@ -171,12 +175,12 @@ def _execute(): |
171 | 175 | try: |
172 | 176 | frame = future.result(timeout=timeout_secs) |
173 | 177 | except concurrent.futures.TimeoutError: |
174 | | - close = getattr(connection, "close", None) |
175 | | - if callable(close): |
176 | | - try: |
177 | | - close() |
178 | | - except Exception: # noqa: BLE001 |
179 | | - pass |
| 178 | + # No cancellation attempt: the pool has a single worker and a |
| 179 | + # single task, so by the time this fires ``_execute`` is already |
| 180 | + # running and ``future.cancel()`` would return False. The Rust |
| 181 | + # engine exposes no way to interrupt an in-flight query, so the |
| 182 | + # statement runs to completion and the ``finally`` block below |
| 183 | + # defers closing the connection until it does. |
180 | 184 | raise SqlTimeout( |
181 | 185 | f"Spatial SQL timed out after {int(timeout_secs)} seconds" |
182 | 186 | ) from None |
@@ -226,9 +230,17 @@ def _execute(): |
226 | 230 | finally: |
227 | 231 | # SedonaDB connections are Rust-backed; release promptly rather than |
228 | 232 | # waiting on GC. Tolerate bindings that expose no close(). |
229 | | - close = getattr(connection, "close", None) |
230 | | - if callable(close): |
231 | | - try: |
232 | | - close() |
233 | | - except Exception: # noqa: BLE001 - best-effort cleanup |
234 | | - pass |
| 233 | + def _close_connection(*_args: object) -> None: |
| 234 | + close = getattr(connection, "close", None) |
| 235 | + if callable(close): |
| 236 | + try: |
| 237 | + close() |
| 238 | + except Exception: # noqa: BLE001 - best-effort cleanup |
| 239 | + # A failed close can strand Rust-backed resources, so give |
| 240 | + # operators a signal. The SQL text is deliberately omitted. |
| 241 | + logger.warning("Failed to close the SedonaDB connection", exc_info=True) |
| 242 | + |
| 243 | + if future is not None and not future.done(): |
| 244 | + future.add_done_callback(_close_connection) |
| 245 | + else: |
| 246 | + _close_connection() |
0 commit comments