@@ -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.
@@ -689,18 +688,28 @@ def close_command(self, command_id: CommandId) -> None:
689688 pass
690689
691690 def get_query_state (self , command_id : CommandId ) -> CommandState :
692- # Server is the source of truth for async command state. Re-attach
693- # to the statement by its id and read the state the server reports
694- # — no connector-side state to drift. SEA keys GetStatementStatus
695- # purely on the id, so a statement the connector no longer holds a
696- # handle for (or never held — a different process) is still
697- # queryable. CLOSED comes straight from the server: after a
691+ # Server is the source of truth for async command state. Use the
692+ # retained owning handle before result streaming starts so kernel
693+ # async statement telemetry is finalized on the original
694+ # ExecuteStatementAsync telemetry object. Once result streaming
695+ # has been claimed (or when this connector never held the handle
696+ # — cross-process / fresh-cursor cases), re-attach to the
697+ # statement by id. SEA keys GetStatementStatus purely on the id,
698+ # so a statement the connector no longer holds a handle for is
699+ # still queryable. CLOSED comes straight from the server: after a
698700 # statement is closed (DELETE) the server still returns 200
699701 # state=CLOSED until the result TTL elapses.
700702 if self ._kernel_session is None :
701703 raise InterfaceError ("get_query_state requires an open session." )
704+ with self ._async_handles_lock :
705+ handle = (
706+ None
707+ if command_id .guid in self ._async_result_stream_started
708+ else self ._async_handles .get (command_id .guid )
709+ )
702710 try :
703- handle = self ._kernel_session .attach_async_statement (command_id .guid )
711+ if handle is None :
712+ handle = self ._kernel_session .attach_async_statement (command_id .guid )
704713 state , failure = handle .status ()
705714 except Exception as exc :
706715 if _is_not_found (exc ):
0 commit comments