Skip to content

[Bug]: asyncstdlib.tee() doesn't preserve buffered state when creating multiple consumers #177

@Atry

Description

@Atry

What happened?

The asyncstdlib.tee() function does not behave consistently with Python's built-in itertools.tee() when creating multiple consumers from an already-advanced iterator. Specifically, asyncstdlib.tee() advances all consumers to the current position of the source iterator, while itertools.tee() correctly preserves a buffer of previously yielded values.

Environment

  • asyncstdlib version: 3.13.1
  • Python version: 3.12.10
  • Operating System: Linux

Expected Behavior

When using itertools.tee() with Python's built-in iterators, creating a new consumer from an already-advanced source iterator should provide access to previously yielded values through internal buffering.

Actual Behavior

When using asyncstdlib.tee(), creating a new consumer from an already-advanced source iterator causes the new consumer to start from the current position, losing access to previously yielded values.

Root Cause Analysis

The issue appears to be that asyncstdlib.tee() doesn't maintain the same buffering mechanism as itertools.tee(). In the standard library implementation, when creating a new consumer from an existing tee'd iterator, the new consumer gains access to the internal buffer that contains previously yielded values.

In asyncstdlib.tee(), it seems that new consumers are created at the current position of the source iterator without access to the historical buffer, causing them to miss previously yielded values.

Minimal Reproducible Example

# Standard library behavior (correct):
# Standard library behavior (correct):
import itertools

# Create initial source
(source,) = itertools.tee(itertools.count(), 1)

# Advance source to position 1
print(next(source))  # 0

# Create first consumer after advancing source
consumer1, = itertools.tee(source, 1)

# Advance source to position 3

print(next(source))  # 1
print(next(source))  # 2
print(next(source))  # 3

# Create second consumer after advancing even more
consumer2, = itertools.tee(source, 1)

# Advance source further
print(next(source))  # 4
print(next(source))  # 5


# Now test the consumers
print(next(consumer1))  # Outputs: 1 (starts from buffered position)
print(next(consumer2))  # Outputs: 4 (starts from buffered position)
print(next(consumer1))  # Outputs: 2 (continues from buffer)
print(next(consumer2))  # Outputs: 5 (continues from buffer)
# asyncstdlib behavior (incorrect):
import itertools
import asyncstdlib

async def infinite_counter():
    for i in itertools.count():
        yield i

# Create initial source
(source,) = asyncstdlib.tee(infinite_counter(), 1)

# Advance source to position 1
await anext(source)  # 0

# Create first consumer after advancing source
consumer1, = asyncstdlib.tee(source, 1)

# Advance source to position 3
await anext(source)  # 1
await anext(source)  # 2
await anext(source)  # 3

# Create second consumer after advancing even more
consumer2, = asyncstdlib.tee(source, 1)

# Advance source further
await anext(source)  # 4
await anext(source)  # 5


# Now test the consumers
print(await anext(consumer1))  # Outputs: 6 (should be 1!)
print(await anext(consumer2))  # Outputs: 7 (should be 4!)
print(await anext(consumer1))  # Outputs: 8 (should be 2!)
print(await anext(consumer2))  # Outputs: 9 (should be 5!)

Request Assignment [Optional]

  • I already understand the cause and want to submit a bugfix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions