mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-31 07:46:51 +00:00
test(python): remove fixed per-test delays from mock HTTP servers
The Python test jobs spend much of their pytest time waiting on the in-process mock HTTP servers. 1. Each server ran `serve_forever()` with the default 0.5 s poll interval, so every `server.shutdown()` blocked for up to 0.5 s. Around 300 tests use such a server (test_remote_db, test_authz/test_catalog, test_listing, the permutation helpers), about 150 s per test job on every platform. A shared `start_server_thread()` helper in `tests/utils.py` serves with a 0.01 s poll interval, so shutdown returns in about 10 ms. 2. The servers bind IPv4 only, but clients used `http://localhost:<port>`. The client tries ::1 first and falls back after the happy-eyeballs delay, about 0.3 s per connection on Windows. The mock handlers close the connection after each response, so one listing test took 9.5 s on Windows CI versus 0.6 s on Linux. Clients now use 127.0.0.1, as test_catalog, test_listing and test_namespace already do. Measured on Windows with the 0.41.0b6 wheel and `-m "not slow and not s3_test"`: full suite 546.7 s to 212.3 s; test_remote_db.py, test_listing.py and test_catalog.py together 284 s to 14 s on current main. Collected test ids are identical (1898) and pass/fail/skip results match. No assertion, test or marker changed.
This commit is contained in:
@@ -3,13 +3,13 @@
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
import json
|
||||
from threading import Thread
|
||||
|
||||
import pytest
|
||||
|
||||
import lancedb
|
||||
from lancedb.db import AsyncConnection, DBConnection
|
||||
from lancedb.remote.errors import HttpError
|
||||
from utils import start_server_thread
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -38,8 +38,7 @@ def catalog_server():
|
||||
do_POST = handle_request
|
||||
|
||||
with ThreadingHTTPServer(("127.0.0.1", 0), Handler) as server:
|
||||
thread = Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
thread = start_server_thread(server, daemon=True)
|
||||
try:
|
||||
yield f"http://127.0.0.1:{server.server_port}", requests, responses
|
||||
finally:
|
||||
|
||||
@@ -15,7 +15,6 @@ import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import urllib.parse
|
||||
from typing import Optional
|
||||
|
||||
@@ -34,6 +33,7 @@ from lancedb.functions import (
|
||||
udf,
|
||||
)
|
||||
from lancedb.secrets import EnvVarSecret
|
||||
from utils import start_server_thread
|
||||
|
||||
THRESHOLD = 20
|
||||
_CACHE = None
|
||||
@@ -295,13 +295,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
self.wfile.write(payload)
|
||||
|
||||
|
||||
server = http.server.ThreadingHTTPServer(("localhost", 0), Handler)
|
||||
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
threading.Thread(
|
||||
target=server.serve_forever, kwargs={"poll_interval": 0.01}, daemon=True
|
||||
).start()
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="API_KEY_SENTINEL",
|
||||
host_override="http://localhost:%d" % server.server_address[1],
|
||||
host_override="http://127.0.0.1:%d" % server.server_address[1],
|
||||
client_config={"retry_config": {"retries": 0}},
|
||||
)
|
||||
db.create_secret("openai-prod", "SECRET_VALUE_SENTINEL")
|
||||
@@ -1731,11 +1733,10 @@ def _mock_remote_function_catalog():
|
||||
response = {"functions": []}
|
||||
self._write_response(200, response)
|
||||
|
||||
with http.server.HTTPServer(("localhost", 0), Handler) as server:
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
with http.server.HTTPServer(("127.0.0.1", 0), Handler) as server:
|
||||
thread = start_server_thread(server)
|
||||
try:
|
||||
yield f"http://localhost:{server.server_address[1]}", state
|
||||
yield f"http://127.0.0.1:{server.server_address[1]}", state
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
|
||||
@@ -5,12 +5,12 @@ import asyncio
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
import json
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
import lancedb
|
||||
from lancedb.remote.errors import HttpError
|
||||
import pytest
|
||||
from utils import start_server_thread
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -31,8 +31,7 @@ def listing_server():
|
||||
do_POST = handle_request
|
||||
|
||||
with ThreadingHTTPServer(("127.0.0.1", 0), Handler) as server:
|
||||
thread = Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
thread = start_server_thread(server, daemon=True)
|
||||
try:
|
||||
yield f"http://127.0.0.1:{server.server_port}", requests, responses
|
||||
finally:
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import contextlib
|
||||
import http.server
|
||||
import json
|
||||
import threading
|
||||
|
||||
import lancedb
|
||||
import pytest
|
||||
@@ -13,6 +12,7 @@ import pyarrow as pa
|
||||
from lancedb.db import AsyncConnection
|
||||
from lancedb.materialized_view import MaterializedViewDefinition
|
||||
from lancedb.remote.db import RemoteDBConnection
|
||||
from utils import start_server_thread
|
||||
|
||||
|
||||
STABLE_ROW_IDS = {"new_table_enable_stable_row_ids": "true"}
|
||||
@@ -50,11 +50,10 @@ def mock_remote_materialized_view_refresh():
|
||||
self.end_headers()
|
||||
self.wfile.write(encoded)
|
||||
|
||||
with http.server.HTTPServer(("localhost", 0), Handler) as server:
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
with http.server.HTTPServer(("127.0.0.1", 0), Handler) as server:
|
||||
thread = start_server_thread(server)
|
||||
try:
|
||||
yield f"http://localhost:{server.server_address[1]}", requests
|
||||
yield f"http://127.0.0.1:{server.server_address[1]}", requests
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
|
||||
@@ -9,12 +9,12 @@ import importlib
|
||||
import warnings
|
||||
import http.server
|
||||
import json
|
||||
import threading
|
||||
from urllib.parse import parse_qs, unquote, urlparse
|
||||
import pytest
|
||||
import pyarrow as pa
|
||||
import lancedb
|
||||
from lance_namespace.errors import NamespaceNotEmptyError, TableNotFoundError
|
||||
from utils import start_server_thread
|
||||
from lancedb.listing import AsyncListing, Listing
|
||||
from lancedb.materialized_view import DEFINITION_FORMAT, DEFINITION_META_KEY
|
||||
from lancedb.namespace import _MAX_QUERY_K, _query_to_namespace_request
|
||||
@@ -267,8 +267,7 @@ def paginated_rest_namespace():
|
||||
pass
|
||||
|
||||
with http.server.HTTPServer(("127.0.0.1", 0), Handler) as server:
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
thread = start_server_thread(server)
|
||||
try:
|
||||
yield {"uri": f"http://127.0.0.1:{server.server_port}"}, requests
|
||||
finally:
|
||||
@@ -1205,7 +1204,7 @@ class TestPushdownOperations:
|
||||
Rust so reads carry the x-lancedb-min-timestamp read-freshness header."""
|
||||
db = lancedb.connect_namespace(
|
||||
"rest",
|
||||
{"uri": "http://localhost:12345"},
|
||||
{"uri": "http://127.0.0.1:12345"},
|
||||
namespace_client_pushdown_operations=["QueryTable"],
|
||||
)
|
||||
assert db._route_pushdown_to_rust is True
|
||||
@@ -1222,7 +1221,7 @@ class TestPushdownOperations:
|
||||
for the async path omitting the freshness header)."""
|
||||
db = lancedb.connect_namespace_async(
|
||||
"rest",
|
||||
{"uri": "http://localhost:12345"},
|
||||
{"uri": "http://127.0.0.1:12345"},
|
||||
namespace_client_pushdown_operations=["QueryTable"],
|
||||
)
|
||||
assert db._route_pushdown_to_rust is True
|
||||
|
||||
@@ -34,6 +34,7 @@ from lancedb.remote import ClientConfig
|
||||
from lancedb.remote.errors import HttpError, RetryError
|
||||
import pytest
|
||||
import pyarrow as pa
|
||||
from utils import start_server_thread
|
||||
|
||||
|
||||
def make_mock_http_handler(handler):
|
||||
@@ -56,16 +57,15 @@ def test_connect_rejects_invalid_cloud_dns_hostname(db_name):
|
||||
@contextlib.contextmanager
|
||||
def mock_lancedb_connection(handler, **client_config):
|
||||
with http.server.HTTPServer(
|
||||
("localhost", 0), make_mock_http_handler(handler)
|
||||
("127.0.0.1", 0), make_mock_http_handler(handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 2},
|
||||
"timeout_config": {
|
||||
@@ -85,16 +85,15 @@ def mock_lancedb_connection(handler, **client_config):
|
||||
@contextlib.asynccontextmanager
|
||||
async def mock_lancedb_connection_async(handler, **client_config):
|
||||
with http.server.HTTPServer(
|
||||
("localhost", 0), make_mock_http_handler(handler)
|
||||
("127.0.0.1", 0), make_mock_http_handler(handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
|
||||
db = await lancedb.connect_async(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 2},
|
||||
"timeout_config": {
|
||||
@@ -660,16 +659,15 @@ def test_remote_table_open_does_not_require_picklable_client_config():
|
||||
request.end_headers()
|
||||
|
||||
with http.server.HTTPServer(
|
||||
("localhost", 0), make_mock_http_handler(handler)
|
||||
("127.0.0.1", 0), make_mock_http_handler(handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
@@ -3249,17 +3247,16 @@ def test_header_provider_with_sync_connection():
|
||||
|
||||
# Create connection with custom client config
|
||||
with http.server.HTTPServer(
|
||||
("localhost", 0), make_mock_http_handler(handler)
|
||||
("127.0.0.1", 0), make_mock_http_handler(handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 2},
|
||||
"timeout_config": {"connect_timeout": 1},
|
||||
@@ -3463,7 +3460,7 @@ def _remote_fork_child(port: int, queue) -> None:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
@@ -3497,17 +3494,16 @@ def test_remote_connection_after_fork():
|
||||
request.end_headers()
|
||||
request.wfile.write(b'{"tables": []}')
|
||||
|
||||
server = http.server.HTTPServer(("localhost", 0), make_mock_http_handler(handler))
|
||||
server = http.server.HTTPServer(("127.0.0.1", 0), make_mock_http_handler(handler))
|
||||
port = server.server_address[1]
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.start()
|
||||
server_thread = start_server_thread(server)
|
||||
try:
|
||||
# Hit the server in the parent first so the runtime + LOOP are warm
|
||||
# before fork; a fresh child must still succeed.
|
||||
parent_db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
@@ -3563,15 +3559,14 @@ def test_inherited_remote_table_reopens_after_fork():
|
||||
request.send_response(404)
|
||||
request.end_headers()
|
||||
|
||||
server = http.server.HTTPServer(("localhost", 0), make_mock_http_handler(handler))
|
||||
server = http.server.HTTPServer(("127.0.0.1", 0), make_mock_http_handler(handler))
|
||||
port = server.server_address[1]
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.start()
|
||||
server_thread = start_server_thread(server)
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
|
||||
@@ -9,13 +9,13 @@ import multiprocessing as mp
|
||||
import pickle
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import lancedb
|
||||
import pyarrow as pa
|
||||
import pytest
|
||||
from lancedb.permutation import Permutation, Permutations, permutation_builder
|
||||
from lancedb.util import tbl_to_tensor
|
||||
from utils import start_server_thread
|
||||
|
||||
torch = pytest.importorskip("torch")
|
||||
|
||||
@@ -100,16 +100,15 @@ def _remote_dataset_handler(request):
|
||||
@contextlib.contextmanager
|
||||
def _remote_dataset_table():
|
||||
with http.server.ThreadingHTTPServer(
|
||||
("localhost", 0), _make_mock_http_handler(_remote_dataset_handler)
|
||||
("127.0.0.1", 0), _make_mock_http_handler(_remote_dataset_handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
@@ -349,7 +348,7 @@ def _remote_multiworker_dataloader_target(port: int, result_queue):
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{port}",
|
||||
host_override=f"http://127.0.0.1:{port}",
|
||||
client_config={
|
||||
"retry_config": {"retries": 0},
|
||||
"timeout_config": {"connect_timeout": 2, "read_timeout": 2},
|
||||
@@ -419,11 +418,10 @@ def test_permutation_dataloader_fork_workers(tmp_path):
|
||||
)
|
||||
def test_remote_permutation_dataloader_fork_workers():
|
||||
with http.server.ThreadingHTTPServer(
|
||||
("localhost", 0), _make_mock_http_handler(_remote_dataset_handler)
|
||||
("127.0.0.1", 0), _make_mock_http_handler(_remote_dataset_handler)
|
||||
) as server:
|
||||
port = server.server_address[1]
|
||||
handle = threading.Thread(target=server.serve_forever)
|
||||
handle.start()
|
||||
handle = start_server_thread(server)
|
||||
try:
|
||||
ctx = mp.get_context("spawn")
|
||||
queue = ctx.Queue()
|
||||
|
||||
@@ -153,6 +153,15 @@ class MockPermutationServer:
|
||||
return self._arrow(request, pa.table({"id": pa.array([], pa.int64())}))
|
||||
|
||||
|
||||
def start_server_thread(server, daemon=False) -> threading.Thread:
|
||||
"""Serve on a thread; a short poll interval lets ``shutdown()`` return promptly."""
|
||||
thread = threading.Thread(
|
||||
target=server.serve_forever, kwargs={"poll_interval": 0.01}, daemon=daemon
|
||||
)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
|
||||
def _make_handler(serve):
|
||||
class MockLanceDBHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
@@ -175,15 +184,14 @@ def mock_remote_table(server):
|
||||
serialize, hiding the prefetch overlap under test.
|
||||
"""
|
||||
with http.server.ThreadingHTTPServer(
|
||||
("localhost", 0), _make_handler(server)
|
||||
("127.0.0.1", 0), _make_handler(server)
|
||||
) as srv:
|
||||
thread = threading.Thread(target=srv.serve_forever)
|
||||
thread.start()
|
||||
thread = start_server_thread(srv)
|
||||
try:
|
||||
db = lancedb.connect(
|
||||
"db://dev",
|
||||
api_key="fake",
|
||||
host_override=f"http://localhost:{srv.server_address[1]}",
|
||||
host_override=f"http://127.0.0.1:{srv.server_address[1]}",
|
||||
client_config={"timeout_config": {"connect_timeout": 5}},
|
||||
)
|
||||
yield db.open_table(server.name)
|
||||
|
||||
Reference in New Issue
Block a user