cloudflare/pingora · error

take_write_lock() called without cache lock

Error message

take_write_lock() called without cache lock

What it means

pingora-cache's HttpCache::take_write_lock() transfers the cache write lock from this request to another (cache-lock leader handoff). The first .expect() (lib.rs:1564) fires when self has no lock_ctx at all: cache locking was never enabled/acquired for this request, so there is nothing to take, and the process panics with 'take_write_lock() called without cache lock'. The method's doc explicitly requires calling is_cache_lock_writer() first.

Source

Thrown at pingora-cache/src/lib.rs:1564

    }

    /// Maximum number of cache lock retries configured for this request.
    pub fn cache_lock_max_retries(&self) -> Option<usize> {
        self.inner_enabled()
            .lock_ctx
            .as_ref()
            .and_then(|l| l.max_retries)
    }

    /// Take the write lock from this request to transfer it to another one.
    /// # Panic
    ///  Call is_cache_lock_writer() to check first, will panic otherwise.
    pub fn take_write_lock(&mut self) -> (WritePermit, &'static CacheKeyLockImpl) {
        let lock_ctx = self
            .inner_enabled_mut()
            .lock_ctx
            .as_mut()
            .expect("take_write_lock() called without cache lock");
        let lock = lock_ctx
            .lock
            .take()
            .expect("take_write_lock() called without lock");
        match lock {
            Locked::Write(w) => (w, lock_ctx.cache_lock),
            Locked::Read(_) => panic!("take_write_lock() called on read lock"),
        }
    }

    /// Set the write lock, which is usually transferred from [Self::take_write_lock()]
    ///
    /// # Panic
    /// Panics if cache lock was not originally configured for this request.
    // TODO: it may make sense to allow configuring the CacheKeyLock here too that the write permit
    // is associated with
    // (The WritePermit comes from the CacheKeyLock and should be used when releasing from the CacheKeyLock,
    // shouldn't be possible to give a WritePermit to a request using a different CacheKeyLock)

View on GitHub (pinned to 0046038bd4)

Solutions

  1. Gate every call exactly as the docs say: if session.cache.is_cache_lock_writer() { let (permit, lock) = session.cache.take_write_lock(); ... }
  2. Ensure the cache backend actually has a cache lock configured (CacheLock/lock settings) so misses acquire a lock_ctx
  3. Only take the lock from code paths that are guaranteed to run for the cache-miss leader request

Example fix

// before
let (permit, lock) = session.cache.take_write_lock(); // panics without a lock ctx

// after
if session.cache.is_cache_lock_writer() {
    let (permit, lock) = session.cache.take_write_lock();
    // hand off via set_write_lock() on the filler request
}
Defensive patterns

Strategy: validation

Validate before calling

// Required guard, straight from the method's doc comment (lib.rs:1556-1558)
if session.cache.is_cache_lock_writer() {
    let (permit, lock) = session.cache.take_write_lock();
    // ...transfer the permit to the filler request...
}

Prevention

When it happens

Trigger: Calling session.cache.take_write_lock() on a request that never went through the cache-lock acquisition path: cache locking not configured on the cache backend, the request arrived as a cache hit / bypass, or the lock context was already dropped by disable().

Common situations: Custom lock-transfer code (streaming miss, background revalidation fill) invoked from filters that also run on cache hits or non-cacheable requests; enabling cache lock only on some keys; upgrading code written against APIs where lock_ctx was unconditional.

Related errors


AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16). Data as JSON: /api/errors/a48a26c68583706b. Report an issue: GitHub.