Skip to content

Fix kernel async statement telemetry handle - #923

Open
jay-xiao446 wants to merge 5 commits into
mainfrom
jay/kernel-async-telemetry-handle
Open

Fix kernel async statement telemetry handle#923
jay-xiao446 wants to merge 5 commits into
mainfrom
jay/kernel-async-telemetry-handle

Conversation

@jay-xiao446

@jay-xiao446 jay-xiao446 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Use the retained owning kernel async handle for the first full in-process async flow: status polling via get_query_state and the first result fetch via get_execution_result.
  • Mark result streaming once the owning handle is claimed for fetch, then fall back to attach-by-ID for later state/result calls.
  • Preserve attach-by-ID for missing-owner cases such as re-fetch, fresh cursor, or cross-process lookup.
  • Update unit coverage and lifecycle comments for the owning-handle versus attach-by-ID behavior.

Testing

  • .venv/bin/python -m pytest tests/unit/test_kernel_client.py -q

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 1 Low

Looks good — the owning-handle-first / attach-by-id-fallback change is correct: the claim is check-and-set under _async_handles_lock, cleanup is consistent across close_command/close_session, and the new unit + e2e tests cover the main paths. One low-severity note on an unclear telemetry-finalization edge when result-set construction fails after a successful await_result().

stream = handle.await_result()
except Exception as exc:
if uses_owning_handle:
with self._async_handles_lock:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The owning-handle failure path only discards _async_result_stream_started when await_result() raises. If await_result() succeeds (marker stays set) but the subsequent KernelResultSet.__init__arrow_schema() raises and is re-wrapped, the guid remains marked as started. A later retry then takes the attach-by-id (no-op telemetry) branch.

Whether this loses the ExecuteStatementAsync telemetry row depends on when the kernel finalizes it: if finalization happens when await_result() returns, this is harmless (telemetry already committed). If finalization only completes once the result stream is drained, the telemetry is lost on this retry because the owning handle is never reused. The PR's own comments ("first in-process result stream", "clear the claimed marker so a retry can still use the telemetry-bearing owning handle") are ambiguous on this point, and the added test_get_execution_result_owning_handle_failure_can_retry_owning_handle only exercises the await_result()-raises case, not the construct-failure-after-await case. Worth confirming the finalization semantics and, if drain-based, discarding the marker on the construction-failure path too.

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 1 Low

Solid, well-tested fix. The owning-handle-first / attach-by-id-fallback logic is correctly lock-guarded, the claimed marker is reset on await failure to preserve retry-with-owning-handle, and unit + e2e coverage and lifecycle comments are updated to match. One low-severity edge: the result-set-construction failure path (second except) doesn't reset _async_result_stream_started, so a retry after that failure loses the telemetry-bearing owning handle — see inline note. The is_telemetry_enabled short-circuit for use_kernel (overriding even force_enable_telemetry) is intentional and tested.

except Exception as exc:
if uses_owning_handle:
with self._async_handles_lock:
self._async_result_stream_started.discard(command_id.guid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The retry-preservation logic is asymmetric. _async_result_stream_started is added under the lock before await_result() runs, and the first except (await failure) correctly discards the marker so a retry can reuse the telemetry-bearing owning handle. But if await_result() succeeds and the subsequent KernelResultSet(...) construction raises (the arrow_schema() path this second except exists to map), the marker stays set and the owning handle stays in _async_handles. A retry then takes the attach-by-id fallback — the exact telemetry-loss case this PR fixes — because the owning result stream was never actually drained on the failed attempt.

This is narrow (only when arrow_schema()/result-set construction fails after a successful await_result), and resetting here is itself debatable since re-awaiting the same owning handle may not be safe. Worth a comment noting the intentional gap, or handling it consistently with the first except.

(Anchored to the nearest changed line — see the description for the exact location.)

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 1 Low

Looks good — the async owning-handle ownership model (owning handle for first in-process status/result, attach-by-id for re-fetch/cross-process, marker cleared on close/session-close) is coherent and well covered by the new unit tests, and the use_kernel telemetry short-circuit is intentional (verified against the added test). One Low note about asymmetric marker cleanup when result-set construction fails after a successful await_result().

except Exception as exc:
if uses_owning_handle:
with self._async_handles_lock:
self._async_result_stream_started.discard(command_id.guid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — Asymmetric cleanup of the _async_result_stream_started marker on the owning-handle path.

In get_execution_result, when the owning handle is used, the guid is added to _async_result_stream_started under the lock (line 782). If handle.await_result() raises, the marker is correctly discarded (lines 789-790) so a retry can still use the telemetry-bearing owning handle — this is exactly the behavior the new test_get_execution_result_owning_handle_failure_can_retry_owning_handle locks in.

However, if await_result() succeeds but the following KernelResultSet construction / arrow_schema() call raises (the try/except at 794-797 that this PR leaves in place), the marker is not discarded. Per this PR's own reasoning, kernel async telemetry is finalized when the result is drained — which happens during fetch, after the result set is constructed. So on a construction failure the drain never runs, yet the marker is now sticky: a subsequent get_async_execution_result() retry falls back to attach-by-id (no-op telemetry), losing the ExecuteStatementAsync telemetry row this change exists to preserve.

This is a narrow edge (arrow_schema raising after await succeeds) and telemetry is best-effort, hence Low. If the intent is that a post-await construction failure should still allow a telemetry-preserving retry, discard the marker in the 796-797 handler too (symmetric with the await handler).

(Anchored to the nearest changed line — see the description for the exact location.)

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues identified by the review bot.

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 1 Medium

Looks solid overall — the owning-handle-first / attach-by-id fallback logic, locking, and lifecycle cleanup (close_command / close_session) are consistent and well-tested. One medium concern: the result-set-construction failure path in get_execution_result doesn't clear the _async_result_stream_started marker the way the sibling await_result failure path does, so a retry after a construction error would silently drop to attach-by-id and lose the async telemetry this PR is meant to preserve.

except Exception as exc:
if uses_owning_handle:
with self._async_handles_lock:
self._async_result_stream_started.discard(command_id.guid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium — The result-set construction failure path leaves _async_result_stream_started set, defeating the retry-with-owning-handle guarantee this PR establishes.

In get_execution_result, the owning handle is claimed by adding command_id.guid to _async_result_stream_started (L782) before the result is materialized. There are then two try blocks:

  • await_result() (L783–791): on failure, if uses_owning_handle: discards the marker (L788–790), so a retry re-selects the owning handle and preserves telemetry.
  • _make_result_set(...)KernelResultSet.__init__arrow_schema() (L794–797): on failure, the marker is not discarded.

Per the method's own comment, kernel async telemetry is finalized on the owning handle only when the result stream is drained (during later fetch), not at await_result() time. So if await_result() succeeds but arrow_schema() raises during construction, the caller never receives a usable ResultSet, yet the marker stays set (and the owning handle is still in _async_handles, since this method uses .get() not .pop()). A subsequent retry then hits the attach-by-id branch and loses the SEA async-statement telemetry row — exactly the regression this PR aims to prevent, and inconsistent with the sibling await_result handling.

Consider discarding the marker on this path too (mirroring L788–790), e.g. wrap the construction failure with the same if uses_owning_handle: cleanup. Note test_get_execution_result_owning_handle_failure_can_retry_owning_handle only covers the await_result failure path; the construction-failure retry case is untested.

(Anchored to the nearest changed line — see the description for the exact location.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants