Repository navigation
Replace the cross-thread cancel example in the Spark documentation #1270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -277,22 +277,13 @@ Athena cancels the calculation on a best-effort basis: | |
| - Canceling a calculation that has already finished does not raise an error or change its state. | ||
|
|
||
| The `execute()` method raises `OperationalError` when the calculation ends in the `CANCELED` state. | ||
| The following example cancels the calculation from another thread after 60 seconds: | ||
|
|
||
| ```python | ||
| import threading | ||
| from pyathena import connect | ||
| from pyathena.spark.cursor import SparkCursor | ||
| As `pyathena.threadsafety = 2` declares, calling `cancel()` from another thread while `execute()` runs on the same cursor is not supported. | ||
| A long calculation can be stopped in the following ways: | ||
|
|
||
| conn = connect(work_group="YOUR_SPARK_WORKGROUP", cursor_class=SparkCursor) | ||
| with conn.cursor() as cursor: | ||
| timer = threading.Timer(60, cursor.cancel) | ||
| timer.start() | ||
| try: | ||
| cursor.execute("""print(spark.read.parquet("s3://YOUR_S3_BUCKET/large_dataset/").count())""") | ||
| finally: | ||
| timer.cancel() | ||
| ``` | ||
| - A `KeyboardInterrupt` while `execute()` runs, with `kill_on_interrupt` enabled, as described below. | ||
| - {ref}`async-spark-cursor`, whose `execute()` returns the calculation ID with a future, and whose `cancel()` takes that ID. | ||
| - {ref}`aio-spark-cursor` with `asyncio.wait_for()`, whose timeout cancels the calculation. | ||
|
|
||
| With `kill_on_interrupt` enabled, which is the default, a `KeyboardInterrupt` while `execute()` waits for the calculation | ||
| requests cancellation, waits until the calculation reaches a terminal state, and then propagates. | ||
|
|
@@ -535,3 +526,23 @@ Cancelling the task while `execute()` is still starting the calculation first wa | |
| and then cancels the calculation it started in the same way. | ||
| If the task is cancelled before `execute()` begins the request, the request is never sent. | ||
| Cancelling the task again during this wait raises `asyncio.CancelledError` at once without cancelling the calculation. | ||
|
|
||
| With `kill_on_interrupt` enabled, a timeout from `asyncio.wait_for()` while `execute()` starts or waits for the calculation therefore cancels it and raises `asyncio.TimeoutError`. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round 2 (claims, callers, documentation reader): CLEAN Base Claims checked:
Limits: no live Spark run, since this is a documentation-only change. The live task-cancellation path is covered by
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Independent review (relayed): Codex CLI 0.163.0 ( Result: CLEAN. Covered: both changed hunks; Non-actionable notes: the example follows the page's top-level-await convention (a script needs an async function and |
||
|
|
||
| ```python | ||
| import asyncio | ||
| from pyathena import aio_connect | ||
| from pyathena.aio.spark.cursor import AioSparkCursor | ||
|
|
||
| async with await aio_connect(work_group="YOUR_SPARK_WORKGROUP", | ||
| cursor_class=AioSparkCursor) as conn: | ||
| cursor = await asyncio.to_thread(conn.cursor) | ||
| async with cursor: | ||
| try: | ||
| await asyncio.wait_for( | ||
| cursor.execute("""print(spark.read.parquet("s3://YOUR_S3_BUCKET/large_dataset/").count())"""), | ||
| timeout=60, | ||
| ) | ||
| except asyncio.TimeoutError: | ||
| print(f"Calculation timed out: {cursor.calculation_id} ({cursor.state})") | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review round 1 (implementation behavior): CLEAN
Base
e086eac7bb2120e434c08c2eababccd9d0167fb0, head32dbdd45bf46285c1fc5c77d3a55bc02795e9c19. Scope:docs/spark.md(Cancellation and AioSparkCursor sections).cancel()usage in the docs (grepfor Timer and "another thread" acrossdocs/andpyathena/spark,pyathena/aio/spark).docs/sqlalchemy.mdcancels from another thread through a new cursor on the shared connection, whichthreadsafety = 2allows.SparkCursor._calculate/_poll(KeyboardInterrupt),AsyncSparkCursor.execute()returning(calculation_id, future)andcancel(query_id)(pyathena/spark/async_cursor.py:205-253), andAioSparkCursor._calculate/_poll(pyathena/aio/spark/cursor.py:98-257), which cancel the calculation on task cancellation withkill_on_interrupt.create_task+await cursor.cancel()example runs on one event loop thread, so it is not affected and is kept.SparkCursor.cancel()from another thread (tests/pyathena/spark/test_spark_cursor.py:162) are kept, as decided in Break result sets after failed or cancelled fetches and drop the cross-thread guards #1238.