DG47 opened a new pull request, #75975:
URL: https://github.com/apache/airflow/pull/75975

   <!-- 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 
00ea612612, which changes `PostgresHook`, 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 has no effect when `PostgresHook` runs on 
psycopg2. That's the driver it uses whenever psycopg 3 or SQLAlchemy 2 is 
missing, which includes every Airflow 2.11 install. psycopg2 waits for a 
statement inside libpq, so Python can't run the `SIGTERM` handler (`on_kill`) 
or the `SIGALRM` handler of `execution_timeout` until the statement finishes. 
On mark failed, the supervisor SIGKILLs the task after 5 s and the statement 
keeps running on the server. On `execution_timeout`, the timeout isn't even 
raised until the statement returns.
   
   **Change:** while a statement runs, `PostgresHook` installs a psycopg2 wait 
callback that waits in `select()` (`psycopg2.extras.wait_select`), where Python 
runs signal handlers:
   
   * **Mark failed:** `on_kill` runs mid-statement, and the existing 
`cancel_query()` sends `connection.cancel()`.
   * **`execution_timeout`:** `AirflowTaskTimeout` is raised inside the 
callback. psycopg2 closes the connection when a wait callback raises, so the 
callback cancels the statement first. `cancel_query()` then reports the 
statement as already cancelled, instead of trying a closed connection.
   
   The wait callback is a process-wide psycopg2 setting, and `copy_expert` 
refuses to run while one is set, so the hook keeps it narrow. It installs the 
callback only:
   
   * for the duration of the statement (`_track_running_cursor`),
   * in the main thread, which is the only one where Python runs signal 
handlers,
   * when no other wait callback is set (for example one for gevent),
   * when `cancel_on_interrupt` / `cancel_on_kill` is enabled.
   
   psycopg 3 is unchanged.
   
   **Verified on a real Airflow 3.3.2** (LocalExecutor, Postgres 17, psycopg 3 
uninstalled so the hook falls back to psycopg2, the same harness as in #74279):
   
   | kill | before (#74279 only) | with this PR |
   |---|---|---|
   | mark failed, `cancel_on_kill=True` | `on_kill` never runs; task SIGKILLed 
after 5 s; statement keeps running on the server | cancelled through 
`connection.cancel()` 0.4 s after SIGTERM (`QueryCanceled`) |
   | `execution_timeout`, `cancel_on_kill=True` | timeout only raised when the 
statement returns | cancelled 0.1 s after the timeout; task fails with 
`AirflowTaskTimeout` |
   | either, `cancel_on_kill=False` | statement keeps running | statement keeps 
running, as designed |
   
   **Tests:**
   
   * Unit tests (mocked psycopg2) check that the callback:
     * is installed only while a statement runs, and removed after a failure,
     * is not installed with `cancel_on_interrupt=False`, from a non-main 
thread, or over an existing callback,
     * cancels on interruption,
     * makes `cancel_query()` handle a connection closed by the callback.
   * Two `backend("postgres")` tests run `pg_sleep(20)` through psycopg2 and 
interrupt it from a signal handler after 0.5 s: once raising 
`AirflowTaskTimeout`, once calling `cancel_query()`. They check that the 
statement is gone from `pg_stat_activity`. Without this change both take the 
full 20 s and fail.
   * `providers/postgres/tests/unit/postgres`: 177 passed with psycopg 3 and 
177 passed with psycopg2 only. The hook tests with `--backend postgres`: 23 
passed with psycopg 3 and 30 passed with psycopg2 only.
   
   The postgres 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]

Reply via email to