{"record":{"id":"74eee506d7d23138","repo":"python/cpython","slug":"semaphore-initial-value-must-be-0","errorCode":null,"errorMessage":"Semaphore initial value must be >= 0","messagePattern":"Semaphore initial value must be >= 0","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/locks.py","lineNumber":366,"sourceCode":"\nclass Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):\n    \"\"\"A Semaphore implementation.\n\n    A semaphore manages an internal counter which is decremented by each\n    acquire() call and incremented by each release() call.  The counter\n    can never go below zero; when acquire() finds that it is zero, it\n    blocks, waiting until some other thread calls release().\n\n    Semaphores also support the context management protocol.\n\n    The optional argument gives the initial value for the internal\n    counter; it defaults to 1. If the value given is less than 0,\n    ValueError is raised.\n    \"\"\"\n\n    def __init__(self, value=1):\n        if value < 0:\n            raise ValueError(\"Semaphore initial value must be >= 0\")\n        self._waiters = None\n        self._value = value\n\n    def __repr__(self):\n        res = super().__repr__()\n        extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'\n        if self._waiters:\n            extra = f'{extra}, waiters:{len(self._waiters)}'\n        return f'<{res[1:-1]} [{extra}]>'\n\n    def locked(self):\n        \"\"\"Returns True if semaphore cannot be acquired immediately.\"\"\"\n        # Due to state, or FIFO rules (must allow others to run first).\n        return self._value == 0 or (\n            any(not w.cancelled() for w in (self._waiters or ())))\n\n    async def acquire(self):\n        \"\"\"Acquire a semaphore.","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/locks.py#L348-L384","documentation":"asyncio.Semaphore.__init__ validates that the initial counter value is non-negative; a negative value would make the semaphore permanently un-acquirable and its invariant (value >= 0) meaningless, so ValueError is raised at construction.","triggerScenarios":"asyncio.Semaphore(-1) directly; computing the value from configuration/limits that can go negative (e.g. capacity - load); Semaphore(value=len(pool) - workers) with an empty pool.","commonSituations":"Config-derived concurrency limits; YAML/ENV-provided values parsed without bounds checking; arithmetic on sizes that underflow to negative on small deployments.","solutions":["Clamp at the boundary: max(0, value)","Validate configuration before constructing sync primitives; fail fast with a clear config error","Treat 0 explicitly if 'block everything' is intended (0 is legal)"],"exampleFix":"# before\nsem = asyncio.Semaphore(cfg.max_concurrency - cfg.reserved)  # can be < 0\n\n# after\nvalue = max(0, cfg.max_concurrency - cfg.reserved)\nsem = asyncio.Semaphore(value)","handlingStrategy":"validation","validationCode":"value = int(value)\nif value < 0:\n    raise ValueError(f'concurrency must be >= 0, got {value}')\nsem = asyncio.Semaphore(value)","typeGuard":"def is_valid_semaphore_value(v) -> bool:\n    return isinstance(v, int) and v >= 0","tryCatchPattern":null,"preventionTips":["Clamp config-derived limits with max(0, n)","Validate numeric config at load time with clear errors","Remember 0 is a legal 'block-all' value; negative never is"],"tags":["asyncio","semaphore","validation","configuration"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}