mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
The `odbc` and `jdbc` table functions infer the remote table structure
from the XDBC bridge during query analysis, in
`ITableFunctionXDBC::getActualTableStructure`. This request used the
default `ReadSettings`, so `http_max_tries` was `10`: when the bridge (or
the driver behind it) stopped responding mid-request, it retried up to
ten times, each attempt blocking for up to `http_receive_timeout`.
Because this runs during analysis, before any pipeline executor exists,
it cannot observe query cancellation (`KILL QUERY`, `max_execution_time`),
so a cancelled query stayed in the processlist for minutes and tripped
the stress-test hung check ("possible deadlock found").
`startBridgeIfNot` has already verified the bridge responds, so the
metadata request talks to a known-alive local subprocess. Make a single
attempt (`http_max_tries` = 1) and let the error propagate instead of
retrying.
Added an integration test that points the JDBC bridge at a mock which
answers the ping handshake but never responds to `columns_info`, and
asserts the query fails promptly instead of retrying for minutes.
CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=107461&sha=4a93325d05f03a26c28369c7fe7de652a5f2f155&name_0=PR&name_1=Stress%20test%20%28amd_tsan%29
Related: https://github.com/ClickHouse/ClickHouse/pull/107461
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
import os
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from helpers.cluster import ClickHouseCluster
|
|
from helpers.mock_servers import start_mock_servers
|
|
|
|
cluster = ClickHouseCluster(__file__)
|
|
|
|
# Must match <jdbc_bridge><port> in configs/jdbc_bridge.xml.
|
|
MOCK_BRIDGE_PORT = 9020
|
|
|
|
# http_receive_timeout used for the test query. The metadata request blocks for one such
|
|
# timeout per attempt. With the fix it makes a single attempt (~RECEIVE_TIMEOUT seconds);
|
|
# before the fix it retried http_max_tries (= 10) times, taking well over ten times longer.
|
|
RECEIVE_TIMEOUT = 3
|
|
|
|
node = cluster.add_instance(
|
|
"node",
|
|
main_configs=["configs/jdbc_bridge.xml"],
|
|
)
|
|
|
|
|
|
def start_unresponsive_bridge():
|
|
script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mocks")
|
|
# Run the mock inside the server's own container, reachable at 127.0.0.1:MOCK_BRIDGE_PORT.
|
|
start_mock_servers(
|
|
cluster,
|
|
script_dir,
|
|
[("unresponsive_bridge.py", "node", MOCK_BRIDGE_PORT)],
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def started_cluster():
|
|
try:
|
|
cluster.start()
|
|
start_unresponsive_bridge()
|
|
yield cluster
|
|
finally:
|
|
cluster.shutdown()
|
|
|
|
|
|
def test_jdbc_table_function_does_not_hang_on_unresponsive_bridge(started_cluster):
|
|
# The mock bridge answers the ping handshake but never responds to /columns_info, so
|
|
# structure inference for the `jdbc` table function cannot complete. The query must fail
|
|
# promptly (after a single receive timeout) instead of retrying the metadata request and
|
|
# keeping the query alive for minutes — which is what tripped the stress-test hung check.
|
|
start = time.time()
|
|
error = node.query_and_get_error(
|
|
"SELECT * FROM jdbc('dummy_datasource', 'some_table')",
|
|
settings={"http_receive_timeout": RECEIVE_TIMEOUT},
|
|
)
|
|
elapsed = time.time() - start
|
|
|
|
# The unresponsive bridge must make the query fail, not return data.
|
|
assert error != ""
|
|
# One attempt is ~RECEIVE_TIMEOUT seconds; ten retries would be well over 30 seconds.
|
|
assert (
|
|
elapsed < 15
|
|
), f"jdbc structure inference took {elapsed:.1f}s; the bridge metadata request is being retried instead of failing fast"
|