Skip to content
← Back to Functions
Code

Cache Stampede Lock

Creates a short-lived lock so only one request rebuilds an expired cache item while others use stale data or wait.

Function signature

ogAcquireCacheStampedeLock(cache_key, existing_lock = array(), ttl_seconds = 60, owner = '')

Categories

  • Maintenance and Cron

Parameters

cache_keyCanonical cache key or cache subject being protected.existing_lockExisting lock record from shared storage or caller-owned state.ttl_secondsMaximum lock lifetime in seconds before a stale lock can be reviewed.ownerLog-safe owner label for the process or request attempting the lock.

Return value

Public-safe status string returned by the function for explicit controller branching or logging.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and context-specific escaping; validate admin actions, export fields, privacy plans, cache keys, templates, settings, routes, and ecommerce policies before production use.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Determines whether a cache rebuild lock may be acquired for a cache key.
 *
 * The caller must persist the returned lock token atomically in its chosen storage.
 * This function does not pretend to be atomic by itself.
 *
 * @param string $cache_key Cache key being rebuilt.
 * @param array $existing_lock Existing lock data with token and expires_at.
 * @param int $ttl_seconds Lock lifetime in seconds.
 * @param string $owner Optional owner label.
 * @return array Lock decision and token data.
 */
function ogAcquireCacheStampedeLock($cache_key, $existing_lock = array(), $ttl_seconds = 60, $owner = '') {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$cache_key = trim((string)$cache_key);
	$ttl_seconds = (int)$ttl_seconds;
	$owner = trim((string)$owner);

	if (empty($cache_key)) {
		$result['message'] = 'Cache key is required.';
		return $result;
	}
	if ($ttl_seconds < 5) {
		$ttl_seconds = 5;
	}
	if (!is_array($existing_lock)) {
		$existing_lock = array();
	}

	$current_time = time();
	$existing_expires = 0;
	if (!empty($existing_lock['expires_at'])) {
		$existing_expires = (int)$existing_lock['expires_at'];
	}

	if ($existing_expires > $current_time) {
		$result['success'] = true;
		$result['message'] = 'Existing cache rebuild lock is still active.';
		$result['data'] = array(
			'acquired' => false,
			'use_stale' => true,
			'retry_after' => $existing_expires - $current_time,
			'expires_at' => $existing_expires
		);
		return $result;
	}

	$token = bin2hex(random_bytes(16));
	$result['success'] = true;
	$result['message'] = 'Cache rebuild lock may be acquired.';
	$result['data'] = array(
		'acquired' => true,
		'use_stale' => false,
		'cache_key' => $cache_key,
		'lock_token' => $token,
		'owner' => $owner,
		'expires_at' => $current_time + $ttl_seconds
	);

	return $result;
}