@@ -256,13 +256,12 @@ def __init__(
256256 # fire-and-forget ``close_statement``, which would kill the
257257 # still-running async query the moment the handle is dropped. We
258258 # retain it (and its parent ``Statement``) here so the live query
259- # survives until an explicit close. ``get_query_state`` still
260- # re-attaches to the statement by id (the server is the source
261- # of truth for async state). ``get_execution_result`` uses this
262- # owning handle for the first in-process result stream so kernel
263- # async statement telemetry is finalized on the original
264- # ``ExecuteStatementAsync`` telemetry object, then falls back to
265- # attach-by-id for re-fetch / cross-process cases.
259+ # survives until an explicit close. ``get_query_state`` and
260+ # ``get_execution_result`` use this owning handle before result
261+ # streaming starts so kernel async statement telemetry is
262+ # finalized on the original ``ExecuteStatementAsync`` telemetry
263+ # object, then fall back to attach-by-id for re-fetch /
264+ # cross-process cases.
266265 self ._async_handles : Dict [str , Any ] = {}
267266 self ._async_result_stream_started : Set [str ] = set ()
268267 # Parent ``Statement`` objects kept alive alongside async handles.
@@ -692,18 +691,28 @@ def close_command(self, command_id: CommandId) -> None:
692691 pass
693692
694693 def get_query_state (self , command_id : CommandId ) -> CommandState :
695- # Server is the source of truth for async command state. Re-attach
696- # to the statement by its id and read the state the server reports
697- # — no connector-side state to drift. SEA keys GetStatementStatus
698- # purely on the id, so a statement the connector no longer holds a
699- # handle for (or never held — a different process) is still
700- # queryable. CLOSED comes straight from the server: after a
694+ # Server is the source of truth for async command state. Use the
695+ # retained owning handle before result streaming starts so kernel
696+ # async statement telemetry is finalized on the original
697+ # ExecuteStatementAsync telemetry object. Once result streaming
698+ # has been claimed (or when this connector never held the handle
699+ # — cross-process / fresh-cursor cases), re-attach to the
700+ # statement by id. SEA keys GetStatementStatus purely on the id,
701+ # so a statement the connector no longer holds a handle for is
702+ # still queryable. CLOSED comes straight from the server: after a
701703 # statement is closed (DELETE) the server still returns 200
702704 # state=CLOSED until the result TTL elapses.
703705 if self ._kernel_session is None :
704706 raise InterfaceError ("get_query_state requires an open session." )
707+ with self ._async_handles_lock :
708+ handle = (
709+ None
710+ if command_id .guid in self ._async_result_stream_started
711+ else self ._async_handles .get (command_id .guid )
712+ )
705713 try :
706- handle = self ._kernel_session .attach_async_statement (command_id .guid )
714+ if handle is None :
715+ handle = self ._kernel_session .attach_async_statement (command_id .guid )
707716 state , failure = handle .status ()
708717 except Exception as exc :
709718 if _is_not_found (exc ):
0 commit comments