cloudflare/pingora · error
take_write_lock() called without lock
Error message
take_write_lock() called without lock
What it means
The second .expect() inside HttpCache::take_write_lock() (lib.rs:1568): the request does have a lock_ctx, but lock_ctx.lock is already None because Option::take() consumed the permit earlier. That is the exact post-transfer state, so this panic ('take_write_lock() called without lock') means take_write_lock() was called a second time, or another component already transferred the permit while leaving the context behind.
Source
Thrown at pingora-cache/src/lib.rs:1568
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)
pub fn set_write_lock(&mut self, write_lock: WritePermit) {
if let Some(lock_ctx) = self.inner_enabled_mut().lock_ctx.as_mut() {
lock_ctx.lock.replace(Locked::Write(write_lock));
}View on GitHub (pinned to 0046038bd4)
Solutions
- Call take_write_lock() exactly once per request and move the returned (WritePermit, lock) pair onward
- If a handoff can fail midway, restore the permit with session.cache.set_write_lock(permit) before any retry
- Use is_cache_lock_writer() as the single guard: it is false once the permit was taken
Example fix
// before let (p1, lock) = session.cache.take_write_lock(); let (p2, _) = session.cache.take_write_lock(); // panic: lock already taken // after let (permit, lock) = session.cache.take_write_lock(); // re-arm before any second take: session.cache.set_write_lock(permit); let (permit, lock) = session.cache.take_write_lock();
Defensive patterns
Strategy: validation
Validate before calling
// is_cache_lock_writer() is false once the permit was taken;
// use it to make double-takes impossible
fn take_write_lock_once(cache: &mut HttpCache) -> Option<(WritePermit, &'static CacheKeyLockImpl)> {
if cache.is_cache_lock_writer() {
return Some(cache.take_write_lock());
}
None
} Prevention
- Model the write permit as move-only: after take_write_lock() this request owns nothing
- If a handoff can fail midway, restore with set_write_lock(permit) before any retry
- Centralize take/set in a small state machine instead of calling from multiple filters
When it happens
Trigger: Calling take_write_lock() twice on the same request; a retry loop that re-invokes the handoff after a partial failure; two filters both attempting to transfer the write permit.
Common situations: Error paths that took the lock, failed to hand it off, and then retry the take; refactors where set_write_lock() was dropped so the context is never re-armed; duplicated transfer logic in request and response filters.
Related errors
- take_write_lock() called without cache lock
- must have read body buf
- body buf exists once a partial chunk head was buffered
- body buf exists once a chunk was parsed out of it
- cache_key_callback must be implemented when caching is enabl
AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16).
Data as JSON: /api/errors/5f505f05181a07e1.
Report an issue: GitHub.