Against a remote Hrana/sqld endpoint, cursor.execute() (via libsql.connect(database=..., auth_token=...))
can block indefinitely — no exception raised, no way to bound it from Python. There is no timeout parameter anywhere in the Python API to bound a call,
and the hang appears to hold the GIL (or otherwise prevents the interpreter from servicing other threads),
because a concurrent.futures.ThreadPoolExecutor timeout wrapped around the call did not fire.
Environment
libsql (PyPI) 0.1.11
- Underlying Rust crates (from the compiled
.so, via strings): libsql-0.9.22, libsql-hrana-0.9.22
- Python 3.13.7 (CPython)
- Ubuntu 25.10, Linux 6.17.0-41-generic, x86_64
- Server: a self-hosted
sqld instance
- Connection:
libsql.connect(database="https://<host>", auth_token=<token>)
Symptom
- Application does many sequential queries in a loop over one long-lived connection (in our case,
~2 queries per row × ~18,000 rows — see "How we found it" below).
- At some point a query fails with an exception:
Hrana: `http error: `connection closed before message completed``
Our code reconnects (libsql.connect(...) again) and retries the same query once.
- Intermittently, either the retried query, or a later unrelated query on the fresh connection, then
hangs forever — no exception, no return, nothing written to stdout/stderr, until the process is
killed.
Potential Solutions
- Make blocking Hrana I/O bounded by a configurable timeout (
libsql.connect(..., timeout=...) and/or
per-call cursor.execute(..., timeout=...)), so a dead stream always eventually surfaces as an
exception.
- Ensure the native call releases the GIL while blocked on network I/O, so a
Python-level (thread-based) timeout wrapped by the caller can at least recover, even without native
timeout support.
Against a remote Hrana/sqld endpoint,
cursor.execute()(vialibsql.connect(database=..., auth_token=...))can block indefinitely — no exception raised, no way to bound it from Python. There is no timeout parameter anywhere in the Python API to bound a call,
and the hang appears to hold the GIL (or otherwise prevents the interpreter from servicing other threads),
because a
concurrent.futures.ThreadPoolExecutortimeout wrapped around the call did not fire.Environment
libsql(PyPI) 0.1.11.so, viastrings):libsql-0.9.22,libsql-hrana-0.9.22sqldinstancelibsql.connect(database="https://<host>", auth_token=<token>)Symptom
~2 queries per row × ~18,000 rows — see "How we found it" below).
libsql.connect(...)again) and retries the same query once.hangs forever — no exception, no return, nothing written to stdout/stderr, until the process is
killed.
Potential Solutions
libsql.connect(..., timeout=...)and/orper-call
cursor.execute(..., timeout=...)), so a dead stream always eventually surfaces as anexception.
Python-level (thread-based) timeout wrapped by the caller can at least recover, even without native
timeout support.