DG47 opened a new pull request, #75976:
URL: https://github.com/apache/airflow/pull/75976
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
**Depends on #74279.** This branch is stacked on #74279's branch, so until
that PR is merged the diff here also shows its commits. The only new commit is
e869a14f24, which changes `SqliteHook`, its tests and the provider's operator
guide. I'll rebase onto main once #74279 is merged.
This is the follow-up promised in #74279, which added
`DbApiHook.cancel_query()` and `SQLExecuteQueryOperator.on_kill` /
`cancel_on_kill`. As shown there, it changes nothing for `SqliteHook`:
* `sqlite3` doesn't return to Python until a statement completes, so Python
can't run the `SIGTERM` handler (`on_kill`) or the `SIGALRM` handler of
`execution_timeout` during the statement.
* A `sqlite3` connection has no DB-API `cancel()` for `cancel_query()` to
call.
* On a real Airflow, a 15 s `execution_timeout` on a long statement fired
after 5 min 23 s, when the statement finished.
**Change:**
* While a statement runs in the main thread, `SqliteHook` registers a
progress handler (every `progress_handler_interval` = 100,000 SQLite
instructions). Calling it into Python lets pending signal handlers run.
* `SqliteHook.cancel_query()` interrupts the statement with
`sqlite3.Connection.interrupt()`.
* An exception raised by a signal handler inside the progress handler
(`AirflowTaskTimeout`) aborts the statement, but `sqlite3` only reports it
through `sys.unraisablehook` and raises `OperationalError("interrupted")`.
While the statement runs, the hook recognises its own progress handler in the
exception's traceback and raises the original exception instead, so
`execution_timeout` still fails the task with `AirflowTaskTimeout`. I check the
traceback because `unraisable.object` is the callback on 3.11–3.13 but `None`
on 3.14. Other unraisable exceptions go on to the previous hook.
* The handler isn't registered with `cancel_on_interrupt=False` /
`cancel_on_kill=False`, or outside the main thread.
**Side effect:** reporting the exception requires
`sqlite3.enable_callback_tracebacks(True)` while the statement runs, and
`sqlite3` has no getter for that flag, so it's switched back off (its default)
afterwards. During such a statement, an error raised by a user-defined function
registered on the connection is now reported through `sys.unraisablehook`
instead of being discarded. The statement still fails with the same
`OperationalError`. The progress handler costs about 0.15 % on a 20 M-row
recursive CTE (median of 5 runs).
**Verified on a real Airflow 3.3.2** (LocalExecutor, SQLite 3.50.4, the same
harness as in #74279):
| kill | before (#74279 only) | with this PR |
|---|---|---|
| mark failed, `cancel_on_kill=True` | `on_kill` never runs; the statement
stops only because the task is SIGKILLed 5 s later | interrupted through
`connection.interrupt()` 0.2 s after SIGTERM |
| `execution_timeout` (15 s), `cancel_on_kill=True` | fired after 5 min 23
s, when the statement completed | interrupted 0.1 s after the timeout; task
fails with `AirflowTaskTimeout` |
| either, `cancel_on_kill=False` | unchanged | unchanged, as designed |
**Tests:**
* Real in-memory SQLite tests run a recursive CTE that would take well over
10 s, and interrupt it from a signal handler after 0.5 s: once raising
`AirflowTaskTimeout`, once calling `cancel_query()`. Without this change both
run to completion (20 s) and fail.
* A user-defined function error still fails the statement and reaches the
previous `sys.unraisablehook`.
* Mocked tests check when the progress handler is installed and removed.
* `providers/sqlite/tests/unit/sqlite`: 28 passed on Python 3.11, 3.13 and
3.14.
The sqlite operator guide gets a short section on this. The provider's
`common-sql` lower bound doesn't need to change for it to install: the
overrides only run when the base hook calls them, which needs the `common-sql`
release that ships #74279.
related: #74279, #27314
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]