{"record":{"id":"b032b2610dc8aa07","repo":"tokio-rs/tokio","slug":"number-of-permits-overflowed","errorCode":null,"errorMessage":"number of permits overflowed","messagePattern":"number of permits overflowed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"tokio/src/sync/semaphore.rs","lineNumber":1238,"sourceCode":"    /// }\n    ///\n    /// assert_eq!(sem.available_permits(), 0);\n    ///\n    /// // Release all permits in a single batch.\n    /// drop(permit);\n    ///\n    /// assert_eq!(sem.available_permits(), 10);\n    /// ```\n    #[track_caller]\n    pub fn merge(&mut self, mut other: Self) {\n        assert!(\n            std::ptr::eq(self.sem, other.sem),\n            \"merging permits from different semaphore instances\"\n        );\n        self.permits = self\n            .permits\n            .checked_add(other.permits)\n            .expect(\"number of permits overflowed\");\n        other.permits = 0;\n    }\n\n    /// Splits `n` permits from `self` and returns a new [`SemaphorePermit`] instance that holds `n` permits.\n    ///\n    /// If there are insufficient permits and it's not possible to reduce by `n`, returns `None`.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::sync::Arc;\n    /// use tokio::sync::Semaphore;\n    ///\n    /// let sem = Arc::new(Semaphore::new(3));\n    ///\n    /// let mut p1 = sem.try_acquire_many(3).unwrap();\n    /// let p2 = p1.split(1).unwrap();\n    ///","sourceCodeStart":1220,"sourceCodeEnd":1256,"githubUrl":"https://github.com/tokio-rs/tokio/blob/7d0d729d8f03a0033d6752730d0fb5928962560e/tokio/src/sync/semaphore.rs#L1220-L1256","documentation":"SemaphorePermit::merge (tokio/src/sync/semaphore.rs:1238) combines another permit's permit count into this permit with checked_add and panics with \"number of permits overflowed\" when the sum exceeds what a permit count can represent (above Semaphore::MAX_PERMITS, usize::MAX >> 3 on 64-bit). It is a deliberate fail-fast because permits are plain counts with no overflow path. In practice the sum of two valid permits can only overflow if the counts are already astronomically large or were constructed/merged incorrectly.","triggerScenarios":"Calling permit.merge(other) on a SemaphorePermit (from acquire_many/try_acquire_many) where self.permits + other.permits overflows the internal count type — realistically only when permits with near-MAX_PERMITS counts are merged, or when permit bookkeeping is duplicated (e.g. the same permit merged repeatedly after zeroing failed, or permits forged via forget + custom counts).","commonSituations":"Rare in practice: aggregation logic that pools many permits into one and keeps merging after counts grow unboundedly, buggy wrappers that clone/re-use permit values, or generic code merging permits from different sources whose guard asserts the same semaphore but not the magnitude.","solutions":["Re-analyze whether merging is needed: acquire a single permit of the total size with semaphore.acquire_many(n) instead of merging separately acquired permits.","Check magnitudes before merging: only merge when other.permits + self.permits stays within Semaphore::MAX_PERMITS; otherwise forget one and re-acquire_many the combined size.","Drop (drop/forget appropriately) and re-acquire a fresh permit sized to the total rather than accumulating counts on one permit.","If you need unbounded accounting, track counts in your own numeric type and use the semaphore only for the currently held amount."],"exampleFix":"// before: unbounded accumulation can overflow\nfor p in permits { permit.merge(p); }\n\n// after: bound the merged count\nconst LIMIT: usize = tokio::sync::Semaphore::MAX_PERMITS;\nfor p in permits {\n    if permit.num_permits() + p.num_permits() <= LIMIT {\n        permit.merge(p);\n    } else {\n        p.forget(); // or drop, then re-acquire_many(total) if needed\n    }\n}","handlingStrategy":"validation","validationCode":"const MAX: usize = tokio::sync::Semaphore::MAX_PERMITS;\nassert!(permit.num_permits().checked_add(other.num_permits()).map_or(false, |s| s <= MAX), \"merge would overflow permits\");","typeGuard":"fn can_merge(a: &tokio::sync::SemaphorePermit<'_>, b: &tokio::sync::SemaphorePermit<'_>) -> bool {\n    a.num_permits().checked_add(b.num_permits()).map_or(false, |s| s <= tokio::sync::Semaphore::MAX_PERMITS)\n}","tryCatchPattern":"// merge panics (no Result), so the guard must run before the call:\nif can_merge(&permit, &other) { permit.merge(other); } else { /* re-acquire_many(total) instead */ }","preventionTips":["Check num_permits() sums against Semaphore::MAX_PERMITS before every merge.","Avoid merging permits in unbounded loops; acquire_many the total instead.","Never clone/forge permit values outside the semaphore APIs — counts must come from acquire/try_acquire.","Use forget/drop deliberately and re-acquire fresh, correctly sized permits instead of accumulating."],"tags":["tokio","sync","semaphore","panic","overflow","concurrency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"7d0d729d8f03a0033d6752730d0fb5928962560e","analyzedAt":"2026-09-06T15:37:27.972Z","contentChangedAt":"2026-09-06T15:37:27.972Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}