/** Tiny positive float to use when using "minTime" to assert an inequality */
const TINY_POSTIVE = 0.000001;
- /** Seconds of delay after get() where set() storms are a consideration with 'lockTSE' */
- const SET_DELAY_HIGH_SEC = 0.1;
+ /** Milliseconds of delay after get() where set() storms are a consideration with 'lockTSE' */
+ const SET_DELAY_HIGH_MS = 50;
/** Min millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
const RECENT_SET_LOW_MS = 50;
/** Max millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
* @param int $ttl Seconds to live. Special values are:
* - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
* @param array $opts Options map:
- * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
+ * - lag: seconds of replica DB lag. Typically, this is either the replica DB lag
* before the data was read or, if applicable, the replica DB lag before
* the snapshot-isolated transaction the data was read from started.
* Use false to indicate that replication is not running.
* Default: 0 seconds
- * - since : UNIX timestamp of the data in $value. Typically, this is either
+ * - since: UNIX timestamp of the data in $value. Typically, this is either
* the current time the data was read or (if applicable) the time when
* the snapshot-isolated transaction the data was read from started.
* Default: 0 seconds
- * - pending : Whether this data is possibly from an uncommitted write transaction.
+ * - pending: whether this data is possibly from an uncommitted write transaction.
* Generally, other threads should not see values from the future and
* they certainly should not see ones that ended up getting rolled back.
* Default: false
- * - lockTSE : if excessive replication/snapshot lag is detected, then store the value
+ * - lockTSE: if excessive replication/snapshot lag is detected, then store the value
* with this TTL and flag it as stale. This is only useful if the reads for this key
* use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
* then it will still add on to this TTL in the excessive lag scenario.
* Default: WANObjectCache::TSE_NONE
- * - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
+ * - staleTTL: seconds to keep the key around if it is stale. The get()/getMulti()
* methods return such stale values with a $curTTL of 0, and getWithSetCallback()
* will call the regeneration callback in such cases, passing in the old value
* and its as-of time to the callback. This is useful if adaptiveTTL() is used
* on the old value's as-of time when it is verified as still being correct.
* Default: WANObjectCache::STALE_TTL_NONE.
+ * - creating: optimize for the case where the key does not already exist.
+ * Default: false
* @note Options added in 1.28: staleTTL
+ * @note Options added in 1.33: creating
* @return bool Success
*/
final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
$lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
$staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
$age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
+ $creating = $opts['creating'] ?? false;
$lag = $opts['lag'] ?? 0;
// Do not cache potentially uncommitted data as it might get rolled back
// Wrap that value with time/TTL/version metadata
$wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $now );
+ $storeTTL = $ttl + $staleTTL;
- $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
- return ( is_string( $cWrapped ) )
- ? false // key is tombstoned; do nothing
- : $wrapped;
- };
+ if ( $creating ) {
+ $ok = $this->cache->add( self::VALUE_KEY_PREFIX . $key, $wrapped, $storeTTL );
+ } else {
+ $ok = $this->cache->merge(
+ self::VALUE_KEY_PREFIX . $key,
+ function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
+ // A string value means that it is a tombstone; do nothing in that case
+ return ( is_string( $cWrapped ) ) ? false : $wrapped;
+ },
+ $storeTTL,
+ 1 // 1 attempt
+ );
+ }
- return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
+ return $ok;
}
/**
* stampede is worth avoiding. Note that if the key falls out of cache then concurrent
* threads will all run the callback on cache miss until the value is saved in cache.
* The only stampede protection in that case is from duplicate cache sets when the
- * callback takes longer than WANObjectCache::SET_DELAY_HIGH_SEC seconds; consider
+ * callback takes longer than WANObjectCache::SET_DELAY_HIGH_MS milliseconds; consider
* using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
* set, the higher the worst-case staleness of returned values can be. Also note that
* this option does not by itself handle the case of the key simply expiring on account
}
} elseif ( !$useMutex || $hasLock ) {
if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
+ $setOpts['creating'] = ( $curValue === false );
// Save the value unless a lock-winning thread is already expected to do that
$setOpts['lockTSE'] = $lockTSE;
$setOpts['staleTTL'] = $staleTTL;
// consistent hashing).
if ( $lockTSE < 0 || $hasLock ) {
return true; // either not a priori hot or thread has the lock
- } elseif ( $elapsed <= self::SET_DELAY_HIGH_SEC ) {
+ } elseif ( $elapsed <= self::SET_DELAY_HIGH_MS * 1e3 ) {
return true; // not enough time for threads to pile up
}