python/cpython · error · ValueError

Wrong field count in {line!r}

Error message

Wrong field count in {line!r}

What it means

Raised by asyncio queues when task_done() is called more times than items were put(). Every put() increments _unfinished_tasks and every task_done() decrements it; the count may not go below zero. The error means the application's get/task_done bookkeeping is unbalanced — typically task_done() called without a successful get(), or from multiple consumers for one item.

Source

Thrown at Doc/tools/extensions/c_annotations.py:94

    # Defines how much of the struct is exposed. Only relevant for structs.
    # Source: [<item_kind>.*.struct_abi_kind] in stable_abi.toml.
    struct_abi_kind: str


def read_refcount_data(refcount_filename: Path) -> dict[str, RefCountEntry]:
    refcount_data = {}
    refcounts = refcount_filename.read_text(encoding="utf8")
    for line in refcounts.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            # blank lines and comments
            continue

        # Each line is of the form
        # function ':' type ':' [param name] ':' [refcount effect] ':' [comment]
        parts = line.split(":", 4)
        if len(parts) != 5:
            raise ValueError(f"Wrong field count in {line!r}")
        function, type, arg, refcount, _comment = parts

        # Get the entry, creating it if needed:
        try:
            entry = refcount_data[function]
        except KeyError:
            entry = refcount_data[function] = RefCountEntry(function)
        if not refcount or refcount == "null":
            refcount = None
        else:
            refcount = int(refcount)
        # Update the entry with the new parameter
        # or the result information.
        if arg:
            entry.args.append((arg, type, refcount))
        else:
            entry.result_type = type
            entry.result_refs = refcount

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Call task_done() exactly once per successfully gotten item — audit all call sites
  2. Use the canonical worker shape: item = await q.get(); try: process(item) finally: q.task_done()
  3. Remove duplicate task_done() calls in exception handlers or retries; if you re-queue on failure, do it via put() so the counter rises again
  4. If you don't need join(), drop task_done()/join() entirely and use sentinel values or task cancellation for shutdown

Example fix

# before
while True:
    item = await q.get()
    q.task_done()
    await handle(item)   # if handle raises, finally path may call again
# after
while True:
    item = await q.get()
    try:
        await handle(item)
    finally:
        q.task_done()
Defensive patterns

Strategy: validation

Validate before calling

class TrackedQueue(asyncio.Queue):
    def __init__(self):
        super().__init__()
        self._got = set()

    async def get_tracked(self):
        item = await super().get()
        self._got.add(id(item))
        return item

    def task_done_safe(self, item):
        if id(item) not in self._got:
            raise ValueError('task_done without matching get')
        self._got.discard(id(item))
        super().task_done()

Try / catch

try:
    q.task_done()
except ValueError as e:
    if 'too many times' in str(e):
        logger.exception('unbalanced task_done; audit worker bookkeeping')
    raise

Prevention

When it happens

Trigger: Calling q.task_done() without a preceding q.get(); calling task_done() twice per item; a worker that calls task_done() in both a normal branch and an exception/finally branch for the same item; calling task_done() after get_nowait() on an item already accounted for.

Common situations: Worker-pool patterns with try/finally where task_done() is also called in the except path; refactoring a single-consumer queue to multiple consumers without adjusting bookkeeping; retry logic that re-processes an item and calls task_done() again; mixing join() coordination with manual deque operations.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/2083fb8509b09f66. Report an issue: GitHub.