Submission checklist
Package (Required)
Related Issues / PRs
No related issues or pull requests found after searching by the class name, exception text, and ThreadPoolExecutor.map iterable behavior.
Reproduction Steps / Example Code (Python)
from concurrent.futures import ThreadPoolExecutor
from langchain_core.runnables.config import ContextThreadPoolExecutor
def values():
yield from range(3)
with ThreadPoolExecutor(max_workers=2) as executor:
print(list(executor.map(lambda value: value * 2, values())))
# [0, 2, 4]
with ContextThreadPoolExecutor(max_workers=2) as executor:
print(list(executor.map(lambda value: value * 2, values())))
# TypeError: object of type 'generator' has no len()
Error Message and Stack Trace (if applicable)
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File ".../langchain/libs/core/langchain_core/runnables/config.py", line 647, in map
contexts = [copy_context() for _ in range(len(iterables[0]))] # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^
TypeError: object of type 'generator' has no len()
Description
ContextThreadPoolExecutor.map() declares each input as an Iterable, matching concurrent.futures.Executor.map(), but it calls len(iterables[0]) before delegating to the standard-library implementation. Generators and other unsized iterables are therefore rejected even though they are valid inputs to Executor.map().
Expected behavior:
ContextThreadPoolExecutor.map() accepts generators and other iterables supported by ThreadPoolExecutor.map().
- The mapped function still receives a copied caller context.
- Multiple iterables retain the standard shortest-iterable behavior.
Current behavior:
- A generator passed as the first iterable raises
TypeError before any work is submitted.
- The same generator works with
ThreadPoolExecutor.map().
Root cause:
The override preallocates one context per item with range(len(iterables[0])). This assumes that the first iterable is sized and also duplicates responsibility already handled by ContextThreadPoolExecutor.submit().
Proposed implementation
I plan to make the following focused change in ContextThreadPoolExecutor.map():
def map(
self,
fn: Callable[..., T],
*iterables: Iterable[Any],
**kwargs: Any,
) -> Iterator[T]:
return super().map(fn, *iterables, **kwargs)
This works because the standard ThreadPoolExecutor.map() implementation submits each invocation through self.submit(). Dynamic dispatch therefore reaches the existing ContextThreadPoolExecutor.submit() override, which wraps every submitted call with copy_context().run(...). Context propagation remains intact without measuring, copying, or separately consuming any input iterable.
The implementation will:
- Remove the
len(iterables[0]) call and the eager contexts list.
- Remove the custom
_wrapped_fn and its shared contexts.pop() mutation.
- Delegate directly to the superclass so generators, lazy iterables, multiple iterables,
timeout, chunksize, and version-specific map keyword arguments retain standard-library semantics.
- Keep the existing public method signature and return type unchanged.
- Avoid materializing the first iterable as a list, which would otherwise change laziness and increase memory use for large or unbounded inputs.
Regression tests
I plan to add focused unit coverage for ContextThreadPoolExecutor:
- Pass a generator as the first iterable and assert that all mapped results are returned in order.
- Set a
ContextVar in the caller and assert that mapped worker calls receive its value, proving the delegation still uses the context-copying submit() override.
- Map across a generator and a second, shorter iterable and assert standard shortest-iterable behavior.
- Keep the tests deterministic and network-free.
No dependency, exported symbol, or public API change is required.
I reproduced the failure against the latest master at commit dd6081977099b93eb035f81c993434ca90a018ca. I also validated this exact delegation approach locally with generator input, multiple iterables, and ContextVar propagation.
Could a maintainer please assign this issue to me? I would be happy to implement this change and its regression tests once the approach is approved.
AI-assisted investigation disclosure: I used Codex to inspect the current implementation and history, search for duplicates, run the reproduction, and validate the proposed implementation. I reviewed the evidence and implementation plan before submitting.
System Info
System Information
OS: Darwin
OS Version: Darwin Kernel Version 25.5.0: Tue Jun 9 22:27:52 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T8112
Python Version: 3.12.13 (main, Jun 23 2026, 15:44:24) [Clang 22.1.3 ]
Package Information
langchain_core: 1.5.3
langsmith: 0.8.18
langchain_protocol: 0.0.17
langchain_tests: 1.1.9
Optional packages not installed
deepagents
deepagents-cli
Other Dependencies
httpx: 0.28.1
jsonpatch: 1.33
numpy: 2.3.5
orjson: 3.11.6
packaging: 26.0
pydantic: 2.12.5
pytest: 9.0.3
pytest-asyncio: 1.3.0
pytest-benchmark: 5.2.3
pytest-codspeed: 4.3.0
pytest-recording: 0.13.4
pytest-socket: 0.7.0
pyyaml: 6.0.3
requests: 2.33.0
requests-toolbelt: 1.0.0
rich: 14.2.0
syrupy: 5.1.0
tenacity: 9.1.4
typing-extensions: 4.15.0
uuid-utils: 0.16.0
vcrpy: 8.2.1
websockets: 16.0
wrapt: 2.0.1
xxhash: 3.6.0
zstandard: 0.25.0
Submission checklist
Package (Required)
Related Issues / PRs
No related issues or pull requests found after searching by the class name, exception text, and
ThreadPoolExecutor.mapiterable behavior.Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
ContextThreadPoolExecutor.map()declares each input as anIterable, matchingconcurrent.futures.Executor.map(), but it callslen(iterables[0])before delegating to the standard-library implementation. Generators and other unsized iterables are therefore rejected even though they are valid inputs toExecutor.map().Expected behavior:
ContextThreadPoolExecutor.map()accepts generators and other iterables supported byThreadPoolExecutor.map().Current behavior:
TypeErrorbefore any work is submitted.ThreadPoolExecutor.map().Root cause:
The override preallocates one context per item with
range(len(iterables[0])). This assumes that the first iterable is sized and also duplicates responsibility already handled byContextThreadPoolExecutor.submit().Proposed implementation
I plan to make the following focused change in
ContextThreadPoolExecutor.map():This works because the standard
ThreadPoolExecutor.map()implementation submits each invocation throughself.submit(). Dynamic dispatch therefore reaches the existingContextThreadPoolExecutor.submit()override, which wraps every submitted call withcopy_context().run(...). Context propagation remains intact without measuring, copying, or separately consuming any input iterable.The implementation will:
len(iterables[0])call and the eagercontextslist._wrapped_fnand its sharedcontexts.pop()mutation.timeout,chunksize, and version-specific map keyword arguments retain standard-library semantics.Regression tests
I plan to add focused unit coverage for
ContextThreadPoolExecutor:ContextVarin the caller and assert that mapped worker calls receive its value, proving the delegation still uses the context-copyingsubmit()override.No dependency, exported symbol, or public API change is required.
I reproduced the failure against the latest
masterat commitdd6081977099b93eb035f81c993434ca90a018ca. I also validated this exact delegation approach locally with generator input, multiple iterables, andContextVarpropagation.Could a maintainer please assign this issue to me? I would be happy to implement this change and its regression tests once the approach is approved.
AI-assisted investigation disclosure: I used Codex to inspect the current implementation and history, search for duplicates, run the reproduction, and validate the proposed implementation. I reviewed the evidence and implementation plan before submitting.
System Info
System Information
Package Information
Optional packages not installed
Other Dependencies