RustPython/RustPython · error · ValueError

parties must be >= 1

Error message

parties must be >= 1

What it means

asyncio.Barrier (Python 3.11+) requires at least one participating task. Its constructor validates the parties argument immediately and raises ValueError('parties must be >= 1') when parties is 0 or negative, because a barrier that never fills would block forever. The value fixes how many wait() calls must arrive before all waiting tasks are released simultaneously.

Source

Thrown at Lib/asyncio/locks.py:488

    FILLING = 'filling'
    DRAINING = 'draining'
    RESETTING = 'resetting'
    BROKEN = 'broken'


class Barrier(mixins._LoopBoundMixin):
    """Asyncio equivalent to threading.Barrier

    Implements a Barrier primitive.
    Useful for synchronizing a fixed number of tasks at known synchronization
    points. Tasks block on 'wait()' and are simultaneously awoken once they
    have all made their call.
    """

    def __init__(self, parties):
        """Create a barrier, initialised to 'parties' tasks."""
        if parties < 1:
            raise ValueError('parties must be >= 1')

        self._cond = Condition() # notify all tasks when state changes

        self._parties = parties
        self._state = _BarrierState.FILLING
        self._count = 0       # count tasks in Barrier

    def __repr__(self):
        res = super().__repr__()
        extra = f'{self._state.value}'
        if not self.broken:
            extra += f', waiters:{self.n_waiting}/{self.parties}'
        return f'<{res[1:-1]} [{extra}]>'

    async def __aenter__(self):
        # wait for the barrier reaches the parties number
        # when start draining release and return index of waited task
        return await self.wait()

View on GitHub (pinned to aaeab4f754)

Solutions

  1. Validate the count before constructing: if num_workers < 1, raise a clear configuration error naming the offending value
  2. If 0 participants means 'no synchronization needed', skip creating and awaiting the barrier entirely for that run
  3. Only as a last resort clamp with max(1, parties) when 0 genuinely denotes a single-participant barrier

Example fix

# before
barrier = asyncio.Barrier(num_workers)  # num_workers == 0 at runtime

# after
if num_workers < 1:
    raise ValueError(f"num_workers must be >= 1, got {num_workers}")
barrier = asyncio.Barrier(num_workers)
Defensive patterns

Strategy: validation

Validate before calling

def check_parties(n) -> None:
    if not isinstance(n, int) or isinstance(n, bool) or n < 1:
        raise ValueError(f"Barrier parties must be an int >= 1, got {n!r}")

check_parties(num_workers)
barrier = asyncio.Barrier(num_workers)

Type guard

def is_valid_parties(n) -> bool:
    return isinstance(n, int) and not isinstance(n, bool) and n >= 1

Prevention

When it happens

Trigger: Constructing asyncio.Barrier(0) or asyncio.Barrier(-n); passing a dynamically computed worker count that can be 0, e.g. asyncio.Barrier(len(tasks)) when the task list is empty; threading.Barrier code ported to asyncio where the parties argument was optional or defaulted.

Common situations: Fan-out/fan-in stages whose participant list can legitimately be empty under certain configs; worker-pool sizes read from user config or environment variables without bounds checking; refactors that changed a 'default to 1' behavior into an explicit count.

Related errors


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/52b37b6db52f59af. Report an issue: GitHub.