Cursor.close() leaks server-side handle for async commands that were never fetched

Offen Anfängerfreundlich
#791 5 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

@nanookclaw arbeitet bereits daran.

Seit 20.5.2026.

  • #794 von @nanookclaw — offen

Bewertung

Schwierigkeit
2/5
Geschätzter Aufwand
1-3 Stunden
Anfängerfreundlichkeit
72/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Ruhig
Tech-Stack
python, sql
Bereich
backend, databases

Rechercherichtung

Beginnen Sie in src/databricks/sql/client.py bei Cursor.close(), Zeilen 1713-1718, und untersuchen Sie anschließend die close_command-Implementierungen des Thrift-, SEA- und Kernel-Backends. Reproduzieren Sie eine asynchrone Übermittlung, die niemals get_execution_result() aufruft, schließen Sie den Cursor und überprüfen Sie, dass das serverseitige Handle freigegeben wird, ohne die Bereinigung aktiver Ergebnisse oder das Backend-Verhalten zu beeinträchtigen.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

bug engineer-bot

Summary

Cursor.close() does not free the server-side statement handle for async-submitted commands when get_execution_result() was never called. The handle leaks until the session closes (where backend-specific sweeps may or may not catch it).

Repro

import databricks.sql

with databricks.sql.connect(...) as conn:
    cur = conn.cursor()
    cur.execute("SELECT * FROM huge_table", async_op=True)
    # User decides not to wait for the result.
    cur.close()
    # Server-side ExecutedAsyncStatement still tracked until conn.close().

Root cause

Cursor.close() at src/databricks/sql/client.py:1713-1718 only nulls active_command_id and closes active_result_set — but for the async-submit-without-fetch case, active_result_set is None, so backend.close_command(active_command_id) is never invoked.

def close(self) -> None:
    """Close cursor"""
    self.open = False
    self.active_command_id = None
    if self.active_result_set:
        self._close_and_clear_active_result_set()

Scope

Affects all three backends: Thrift, SEA, and the new kernel backend. Each one tracks the async handle differently (Thrift via Thrift-server-side state; SEA via the SEA statement; kernel via _async_handles dict), but the shared Cursor.close() path doesn't notify any of them about the unfetched async handle.

Suggested fix

Single change in shared Cursor.close():

def close(self) -> None:
    self.open = False
    if self.active_result_set:
        self._close_and_clear_active_result_set()
    elif self.active_command_id is not None:
        # async submission that never had get_execution_result called —
        # the result-set close path didn't fire, so issue an explicit
        # close_command to free the server-side handle.
        try:
            self.backend.close_command(self.active_command_id)
        except Exception as exc:
            logger.warning("close_command on cursor close failed: %s", exc)
    self.active_command_id = None

Safe across all backends: Thrift's close_command sends TCloseOperation (valid call), SEA's sends a cancel/close, kernel's is already tolerant of unknown ids (no-op).

Surfaced by

Review of PR #787 (kernel backend). Documented as P0 #1 in https://github.com/databricks/databricks-sql-python/pull/787#issuecomment-4475165482 — but the leak is pre-existing on Thrift and SEA too, not specific to the kernel backend.

Vorherrschende Sprache
Python
Sterne
236
Forks
162
Ø Merge
14 Std. 22 Min.
Gemergte PRs (30 T.)
25

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus databricks/databricks-sql-python

Alle Issues in databricks/databricks-sql-python

Ähnliche Issues

Weitere Issues zu Python

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.