tonhowtf/omniget · critical

host semaphore closed unexpectedly

Error message

host semaphore closed unexpectedly

What it means

In host_limiter, acquire() takes an owned permit from a per-host tokio Semaphore. acquire_owned returns Err only when the semaphore has been closed; the code treats that as an impossible invariant and panics with "host semaphore closed unexpectedly". Closing the semaphore is a programming error somewhere in the limiter.

Solutions

  1. Find and remove the semaphore.close() call (or ensure it happens only when no clones remain).
  2. Don't evict host entries from the map while tasks can still hold Arc<Semaphore> clones; use Arc retained-per-entry or refcount guards.
  3. Replace .expect with graceful handling: on Closed error, re-resolve the semaphore from the map and retry the acquire.
  4. Ensure shutdown drains in-flight acquire() futures before closing semaphores.

Example fix

// before
let permit = semaphore
    .acquire_owned()
    .await
    .expect("host semaphore closed unexpectedly");

// after
let permit = match semaphore.clone().acquire_owned().await {
    Ok(permit) => permit,
    Err(_closed) => {
        // semaphore was closed; re-resolve and retry once
        let fresh = state().lock().await.semaphore_for(host_key).clone();
        fresh.acquire_owned().await.expect("host semaphore closed unexpectedly")
    }
};
Defensive patterns

Strategy: retry

Try / catch

loop {
    match semaphore.clone().acquire_owned().await {
        Ok(p) => break p,
        Err(_) => {
            // re-resolve fresh semaphore from the map, then retry
            semaphore = state().lock().await.semaphore_for(host_key).clone();
        }
    }
}

Prevention

When it happens

Trigger: acquire() is called after something called semaphore.close() on the host's Semaphore (e.g. a shutdown/cleanup path, eviction of the host entry, or a Drop impl closing the shared semaphore while a task still holds a clone).

Common situations: Host map entries evicted/replaced under lock while in-flight tasks still reference the old Arc<Semaphore>; shutdown logic closing semaphores before draining pending acquire calls; refactoring that added close() for cancellation.

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 tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/d2933d51f09d037e. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/core/host_limiter.rs:127

pub struct HostLease {
    _permit: OwnedSemaphorePermit,
}

pub async fn acquire(host_key: &str) -> HostLease {
    let semaphore = {
        let mut guard = state().lock().await;
        guard
            .semaphores
            .entry(host_key.to_string())
            .or_insert_with(|| Arc::new(Semaphore::new(limit_for_host(host_key))))
            .clone()
    };

    let permit = semaphore
        .acquire_owned()
        .await
        .expect("host semaphore closed unexpectedly");

    let interval_ms = interval_ms_for_host(host_key);
    if interval_ms > 0 {
        let wait = {
            let mut guard = state().lock().await;
            let now = std::time::Instant::now();
            let last = guard.last_dispatch.get(host_key).copied();
            let wait = match last {
                Some(t) => {
                    let elapsed = now.duration_since(t).as_millis() as u64;
                    interval_ms.saturating_sub(elapsed)
                }
                None => 0,
            };
            guard.last_dispatch.insert(
                host_key.to_string(),
                now + std::time::Duration::from_millis(wait),
            );

View on GitHub (pinned to 8600b91f42)