This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 494f65e0f68 Fix async engine datetimes being shifted on non-UTC MySQL 
servers (#76505)
494f65e0f68 is described below

commit 494f65e0f682ab8c09cca83f7776de714d638462
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Thu Nov 5 11:45:37 2026 +0800

    Fix async engine datetimes being shifted on non-UTC MySQL servers (#76505)
    
    The async engine skipped the connection setup the sync engine gets, so its
    MySQL sessions kept the server time zone and stored task heartbeats and 
audit
    log rows shifted by the server's UTC offset.
---
 airflow-core/src/airflow/settings.py          |  1 +
 airflow-core/tests/unit/core/test_settings.py | 44 ++++++++++++++++++++++++---
 2 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/settings.py 
b/airflow-core/src/airflow/settings.py
index e5546e26f80..f5a2c460268 100644
--- a/airflow-core/src/airflow/settings.py
+++ b/airflow-core/src/airflow/settings.py
@@ -505,6 +505,7 @@ def _configure_async_session() -> None:
         connect_args=_get_connect_args("async"),
         engine_args=engine_args,
     )
+    setup_event_handlers(async_engine.sync_engine)
     AsyncSession = async_sessionmaker(
         bind=async_engine,
         class_=SAAsyncSession,
diff --git a/airflow-core/tests/unit/core/test_settings.py 
b/airflow-core/tests/unit/core/test_settings.py
index 9fb31ea0251..28f9d074578 100644
--- a/airflow-core/tests/unit/core/test_settings.py
+++ b/airflow-core/tests/unit/core/test_settings.py
@@ -28,6 +28,7 @@ from unittest import mock
 from unittest.mock import MagicMock, call, patch
 
 import pytest
+from sqlalchemy import text
 from sqlalchemy.engine import Engine, make_url
 from sqlalchemy.ext.asyncio import AsyncEngine
 from sqlalchemy.pool import NullPool
@@ -295,8 +296,9 @@ class TestMetadataEngineHooks:
 
         assert mock_create_engine.call_args[1]["connect_args"] == 
{"prepare_threshold": None}
 
+    @patch("airflow.settings.setup_event_handlers")
     @patch("airflow.settings.create_async_metadata_engine")
-    def test_configure_async_session_forwards_the_psycopg3_default(self, 
mock_create_async_engine):
+    def test_configure_async_session_forwards_the_psycopg3_default(self, 
mock_create_async_engine, _):
         """The docs promise the default on both engines, so assert the async 
one separately."""
         from airflow import settings
 
@@ -307,9 +309,10 @@ class TestMetadataEngineHooks:
 
         assert mock_create_async_engine.call_args[1]["connect_args"] == 
{"prepare_threshold": None}
 
+    @patch("airflow.settings.setup_event_handlers")
     @patch("airflow.settings.create_async_metadata_engine")
     def test_configure_async_session_delegates_to_create_async_metadata_engine(
-        self, mock_create_async_engine
+        self, mock_create_async_engine, mock_setup_event_handlers
     ):
         """_configure_async_session() must call create_async_metadata_engine 
with no pool args for sqlite."""
         from airflow import settings
@@ -330,9 +333,11 @@ class TestMetadataEngineHooks:
         assert "connect_args" in call_kwargs[1]
         # sqlite doesn't support pool size args
         assert call_kwargs[1]["engine_args"] == {}
+        
mock_setup_event_handlers.assert_called_once_with(mock_create_async_engine.return_value.sync_engine)
 
+    @patch("airflow.settings.setup_event_handlers")
     @patch("airflow.settings.create_async_metadata_engine")
-    def test_configure_async_session_passes_pool_args_for_non_sqlite(self, 
mock_create_async_engine):
+    def test_configure_async_session_passes_pool_args_for_non_sqlite(self, 
mock_create_async_engine, _):
         """_configure_async_session() must pass pool configuration for 
non-sqlite backends."""
         from airflow import settings
 
@@ -357,8 +362,9 @@ class TestMetadataEngineHooks:
         assert engine_args["pool_pre_ping"] is True
         assert engine_args["max_overflow"] == 5
 
+    @patch("airflow.settings.setup_event_handlers")
     @patch("airflow.settings.create_async_metadata_engine")
-    def test_configure_async_session_uses_nullpool_when_pool_disabled(self, 
mock_create_async_engine):
+    def test_configure_async_session_uses_nullpool_when_pool_disabled(self, 
mock_create_async_engine, _):
         """_configure_async_session() must use NullPool when 
SQL_ALCHEMY_POOL_ENABLED is False."""
         from airflow import settings
 
@@ -768,6 +774,36 @@ def 
test_async_pool_is_closed_before_process_shutdown(driver):
         assert diagnostic not in output, output
 
 
[email protected]_test
[email protected](
+    ("driver", "query", "expected"),
+    [
+        pytest.param(
+            "mysql+aiomysql", "SELECT @@session.time_zone", "+00:00", 
marks=pytest.mark.backend("mysql")
+        ),
+        pytest.param(
+            "mysql+asyncmy", "SELECT @@session.time_zone", "+00:00", 
marks=pytest.mark.backend("mysql")
+        ),
+        pytest.param("sqlite+aiosqlite", "PRAGMA foreign_keys", 1, 
marks=pytest.mark.backend("sqlite")),
+    ],
+)
+def test_async_engine_connections_get_the_sync_engine_setup(monkeypatch, 
driver, query, expected):
+    url = make_url(settings.SQL_ALCHEMY_CONN_ASYNC).set(drivername=driver)
+    monkeypatch.setattr(settings, "SQL_ALCHEMY_CONN_ASYNC", 
url.render_as_string(hide_password=False))
+    monkeypatch.setattr(settings, "async_engine", settings.async_engine)
+    monkeypatch.setattr(settings, "AsyncSession", settings.AsyncSession)
+    settings._configure_async_session()
+
+    async def run():
+        try:
+            async with settings.AsyncSession() as session:
+                return (await session.execute(text(query))).scalar_one()
+        finally:
+            await settings.dispose_async_engine()
+
+    assert asyncio.run(run()) == expected
+
+
 @pytest.mark.parametrize(
     ("sql_alchemy_conn", "expected"),
     [

Reply via email to