tokio-rs/tokio · critical

number of permits overflowed

Error message

number of permits overflowed

What it means

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.

Source

Thrown at tokio/src/sync/semaphore.rs:1238

    /// }
    ///
    /// assert_eq!(sem.available_permits(), 0);
    ///
    /// // Release all permits in a single batch.
    /// drop(permit);
    ///
    /// assert_eq!(sem.available_permits(), 10);
    /// ```
    #[track_caller]
    pub fn merge(&mut self, mut other: Self) {
        assert!(
            std::ptr::eq(self.sem, other.sem),
            "merging permits from different semaphore instances"
        );
        self.permits = self
            .permits
            .checked_add(other.permits)
            .expect("number of permits overflowed");
        other.permits = 0;
    }

    /// Splits `n` permits from `self` and returns a new [`SemaphorePermit`] instance that holds `n` permits.
    ///
    /// If there are insufficient permits and it's not possible to reduce by `n`, returns `None`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::sync::Arc;
    /// use tokio::sync::Semaphore;
    ///
    /// let sem = Arc::new(Semaphore::new(3));
    ///
    /// let mut p1 = sem.try_acquire_many(3).unwrap();
    /// let p2 = p1.split(1).unwrap();
    ///

View on GitHub (pinned to 7d0d729d8f)

Solutions

  1. 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.
  2. 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.
  3. Drop (drop/forget appropriately) and re-acquire a fresh permit sized to the total rather than accumulating counts on one permit.
  4. If you need unbounded accounting, track counts in your own numeric type and use the semaphore only for the currently held amount.

Example fix

// before: unbounded accumulation can overflow
for p in permits { permit.merge(p); }

// after: bound the merged count
const LIMIT: usize = tokio::sync::Semaphore::MAX_PERMITS;
for p in permits {
    if permit.num_permits() + p.num_permits() <= LIMIT {
        permit.merge(p);
    } else {
        p.forget(); // or drop, then re-acquire_many(total) if needed
    }
}
Defensive patterns

Strategy: validation

Validate before calling

const MAX: usize = tokio::sync::Semaphore::MAX_PERMITS;
assert!(permit.num_permits().checked_add(other.num_permits()).map_or(false, |s| s <= MAX), "merge would overflow permits");

Type guard

fn can_merge(a: &tokio::sync::SemaphorePermit<'_>, b: &tokio::sync::SemaphorePermit<'_>) -> bool {
    a.num_permits().checked_add(b.num_permits()).map_or(false, |s| s <= tokio::sync::Semaphore::MAX_PERMITS)
}

Try / catch

// merge panics (no Result), so the guard must run before the call:
if can_merge(&permit, &other) { permit.merge(other); } else { /* re-acquire_many(total) instead */ }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of tokio-rs/tokio@7d0d729d8f (2026-09-06). Data as JSON: /api/errors/b032b2610dc8aa07. Report an issue: GitHub.