@@ -810,17 +810,32 @@ def get_execution_result(
810810 # attach by id for re-fetch. This preserves the Thrift-parity
811811 # behavior where results remain re-callable until explicit close.
812812 #
813+ # Concurrency: the owning handle is shared, and ``await_result()``
814+ # below runs OUTSIDE the lock, so it must not run on the same
815+ # handle a concurrent ``get_query_state`` poll is already using
816+ # for ``status()``. Mirror that method's guard here — if a status
817+ # poll has the owning handle reserved (guid in
818+ # ``_async_status_in_flight``), fall through to attach-by-id and
819+ # get a fresh kernel handle, exactly as an in-flight peer poll
820+ # does. In the normal serial flow (poll to terminal, then fetch)
821+ # the reservation is already discarded, so the fetch still takes
822+ # the telemetry-preserving owning-handle path.
823+ #
813824 # If this process does not hold the owning handle (fresh cursor,
814- # restarted process, already re-fetched), ``attach_async_statement``
815- # issues a GetStatementStatus to seed the handle; a 404 (unknown
816- # / aged-out id) surfaces as a NotFound KernelError mapped to
825+ # restarted process, already re-fetched, or a concurrent poll
826+ # holds it), ``attach_async_statement`` issues a
827+ # GetStatementStatus to seed the handle; a 404 (unknown / aged-out
828+ # id) surfaces as a NotFound KernelError mapped to
817829 # ``ProgrammingError`` below via ``_wrap_kernel_exception``.
818830 if self ._kernel_session is None :
819831 raise InterfaceError ("get_execution_result requires an open session." )
820832 with self ._async_handles_lock :
821833 handle = (
822834 None
823- if command_id .guid in self ._async_result_stream_started
835+ if (
836+ command_id .guid in self ._async_result_stream_started
837+ or command_id .guid in self ._async_status_in_flight
838+ )
824839 else self ._async_handles .get (command_id .guid )
825840 )
826841 uses_owning_handle = handle is not None
0 commit comments