Use real time for testing absolute expirations with changeTTL().
Otherwise, backends like memcached or redis will fail since
they do not use the mock time.
Also:
* Make SqlBagOStuff actually override changeTTLMulti() by
using the right method name
* Check TTL_INDEFINITE more explicitly for clarity
* Rename TTL conversion methods for clarity
* Use isRelativeExpiration() in MemcachedBagOStuff
Change-Id: I9365ceb31d4e7bef65906363d42b8c3020a66346
if ( $value === false ) {
$value = $callback( $ttl );
- if ( $value !== false ) {
+ if ( $value !== false && $ttl >= 0 ) {
$this->set( $key, $value, $ttl, $flags );
}
}
unset( $this->bag[$key] );
$this->bag[$key] = [
self::KEY_VAL => $value,
- self::KEY_EXP => $this->convertToExpiry( $exptime ),
+ self::KEY_EXP => $this->getExpirationAsTimestamp( $exptime ),
self::KEY_CAS => $this->token . ':' . ++self::$casCounter
];
* @return bool
*/
protected function doChangeTTL( $key, $exptime, $flags ) {
- $expiry = $this->convertToExpiry( $exptime );
- $delete = ( $expiry != 0 && $expiry < $this->getCurrentTime() );
-
if ( !$this->lock( $key, 0 ) ) {
return false;
}
+
+ $expiry = $this->getExpirationAsTimestamp( $exptime );
+ $delete = ( $expiry != self::TTL_INDEFINITE && $expiry < $this->getCurrentTime() );
+
// Use doGet() to avoid having to trigger resolveSegments()
$blob = $this->doGet( $key, self::READ_LATEST );
if ( $blob ) {
/**
* @param int $exptime
* @return bool
+ * @since 1.34
*/
- final protected function expiryIsRelative( $exptime ) {
- return ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) );
+ final protected function isRelativeExpiration( $exptime ) {
+ return ( $exptime != self::TTL_INDEFINITE && $exptime < ( 10 * self::TTL_YEAR ) );
}
/**
* - positive (>= 10 years): absolute UNIX timestamp; return this value
*
* @param int $exptime
- * @return int Absolute TTL or 0 for indefinite
+ * @return int Expiration timestamp or TTL_INDEFINITE for indefinite
+ * @since 1.34
*/
- final protected function convertToExpiry( $exptime ) {
- return $this->expiryIsRelative( $exptime )
- ? (int)$this->getCurrentTime() + $exptime
+ final protected function getExpirationAsTimestamp( $exptime ) {
+ if ( $exptime == self::TTL_INDEFINITE ) {
+ return $exptime;
+ }
+
+ return $this->isRelativeExpiration( $exptime )
+ ? intval( $this->getCurrentTime() + $exptime )
: $exptime;
}
* - positive (>= 10 years): absolute UNIX timestamp; return offset to current time
*
* @param int $exptime
- * @return int Relative TTL or 0 for indefinite
+ * @return int Relative TTL or TTL_INDEFINITE for indefinite
+ * @since 1.34
*/
- final protected function convertToRelative( $exptime ) {
- return $this->expiryIsRelative( $exptime ) || !$exptime
- ? (int)$exptime
- : max( $exptime - (int)$this->getCurrentTime(), 1 );
+ final protected function getExpirationAsTTL( $exptime ) {
+ if ( $exptime == self::TTL_INDEFINITE ) {
+ return $exptime;
+ }
+
+ return $this->isRelativeExpiration( $exptime )
+ ? $exptime
+ : (int)max( $exptime - $this->getCurrentTime(), 1 );
}
/**
* discarded immediately because the expiry is in the past.
* Clamp expires >30d at 30d, unless they're >=1e9 in which
* case they are likely to really be absolute (1e9 = 2011-09-09)
- * @param int $expiry
+ * @param int $exptime
* @return int
*/
- function fixExpiry( $expiry ) {
- if ( $expiry > 2592000 && $expiry < 1000000000 ) {
- $expiry = 2592000;
- }
- return (int)$expiry;
+ protected function fixExpiry( $exptime ) {
+ return ( $exptime > self::TTL_MONTH && !$this->isRelativeExpiration( $exptime ) )
+ ? self::TTL_MONTH
+ : (int)$exptime;
}
}
return false;
}
- $ttl = $this->convertToRelative( $exptime );
+ $ttl = $this->getExpirationAsTTL( $exptime );
$e = null;
try {
}
}
- $ttl = $this->convertToRelative( $exptime );
+ $ttl = $this->getExpirationAsTTL( $exptime );
$op = $ttl ? 'setex' : 'set';
$result = true;
}
}
- $relative = $this->expiryIsRelative( $exptime );
- $op = ( $exptime == 0 ) ? 'persist' : ( $relative ? 'expire' : 'expireAt' );
+ $relative = $this->isRelativeExpiration( $exptime );
+ $op = ( $exptime == self::TTL_INDEFINITE )
+ ? 'persist'
+ : ( $relative ? 'expire' : 'expireAt' );
$result = true;
foreach ( $batches as $server => $batchKeys ) {
try {
$conn->multi( Redis::PIPELINE );
foreach ( $batchKeys as $key ) {
- if ( $exptime == 0 ) {
+ if ( $exptime == self::TTL_INDEFINITE ) {
$conn->persist( $key );
} elseif ( $relative ) {
- $conn->expire( $key, $this->convertToRelative( $exptime ) );
+ $conn->expire( $key, $this->getExpirationAsTTL( $exptime ) );
} else {
- $conn->expireAt( $key, $this->convertToExpiry( $exptime ) );
+ $conn->expireAt( $key, $this->getExpirationAsTimestamp( $exptime ) );
}
}
$batchResult = $conn->exec();
return false;
}
- $ttl = $this->convertToRelative( $expiry );
+ $ttl = $this->getExpirationAsTTL( $expiry );
try {
$result = $conn->set(
$key,
return false;
}
- $relative = $this->expiryIsRelative( $exptime );
+ $relative = $this->isRelativeExpiration( $exptime );
try {
- if ( $exptime == 0 ) {
+ if ( $exptime == self::TTL_INDEFINITE ) {
$result = $conn->persist( $key );
$this->logRequest( 'persist', $key, $conn->getServer(), $result );
} elseif ( $relative ) {
- $result = $conn->expire( $key, $this->convertToRelative( $exptime ) );
+ $result = $conn->expire( $key, $this->getExpirationAsTTL( $exptime ) );
$this->logRequest( 'expire', $key, $conn->getServer(), $result );
} else {
- $result = $conn->expireAt( $key, $this->convertToExpiry( $exptime ) );
+ $result = $conn->expireAt( $key, $this->getExpirationAsTimestamp( $exptime ) );
$this->logRequest( 'expireAt', $key, $conn->getServer(), $result );
}
} catch ( RedisException $e ) {
protected $connFailureErrors = [];
/** @var int */
- private static $GARBAGE_COLLECT_DELAY_SEC = 1;
+ private static $GC_DELAY_SEC = 1;
/** @var string */
private static $OP_SET = 'set';
# Don't keep timing out trying to connect for each call if the DB is down
if (
isset( $this->connFailureErrors[$serverIndex] ) &&
- ( time() - $this->connFailureTimes[$serverIndex] ) < 60
+ ( $this->getCurrentTime() - $this->connFailureTimes[$serverIndex] ) < 60
) {
throw $this->connFailureErrors[$serverIndex];
}
$keysByTable[$serverIndex][$tableName][] = $key;
}
- $exptime = $this->convertToExpiry( $exptime );
+ $exptime = $this->getExpirationAsTimestamp( $exptime );
$result = true;
/** @noinspection PhpUnusedLocalVariableInspection */
protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
- $exptime = $this->convertToExpiry( $exptime );
+ $exptime = $this->getExpirationAsTimestamp( $exptime );
/** @noinspection PhpUnusedLocalVariableInspection */
$silenceScope = $this->silenceTransactionProfiler();
return $ok;
}
- protected function doChangeTTLMulti( array $keys, $exptime, $flags = 0 ) {
+ public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
return $this->modifyMulti(
array_fill_keys( $keys, null ),
$exptime,
protected function isExpired( $db, $exptime ) {
return (
$exptime != $this->getMaxDateTime( $db ) &&
- wfTimestamp( TS_UNIX, $exptime ) < time()
+ wfTimestamp( TS_UNIX, $exptime ) < $this->getCurrentTime()
);
}
* @return string
*/
protected function getMaxDateTime( $db ) {
- if ( time() > 0x7fffffff ) {
+ if ( (int)$this->getCurrentTime() > 0x7fffffff ) {
return $db->timestamp( 1 << 62 );
} else {
return $db->timestamp( 0x7fffffff );
// Only purge on one in every $this->purgePeriod writes
mt_rand( 0, $this->purgePeriod - 1 ) == 0 &&
// Avoid repeating the delete within a few seconds
- ( time() - $this->lastGarbageCollect ) > self::$GARBAGE_COLLECT_DELAY_SEC
+ ( $this->getCurrentTime() - $this->lastGarbageCollect ) > self::$GC_DELAY_SEC
) {
$garbageCollector = function () use ( $db ) {
- $this->deleteServerObjectsExpiringBefore( $db, time(), null, $this->purgeLimit );
+ $this->deleteServerObjectsExpiringBefore(
+ $db, $this->getCurrentTime(),
+ null,
+ $this->purgeLimit
+ );
$this->lastGarbageCollect = time();
};
if ( $this->asyncHandler ) {
- $this->lastGarbageCollect = time(); // avoid duplicate enqueues
+ $this->lastGarbageCollect = $this->getCurrentTime(); // avoid duplicate enqueues
( $this->asyncHandler )( $garbageCollector );
} else {
$garbageCollector();
}
public function expireAll() {
- $this->deleteObjectsExpiringBefore( time() );
+ $this->deleteObjectsExpiringBefore( $this->getCurrentTime() );
}
public function deleteObjectsExpiringBefore(
protected function markServerDown( DBError $exception, $serverIndex ) {
unset( $this->conns[$serverIndex] ); // bug T103435
+ $now = $this->getCurrentTime();
if ( isset( $this->connFailureTimes[$serverIndex] ) ) {
- if ( time() - $this->connFailureTimes[$serverIndex] >= 60 ) {
+ if ( $now - $this->connFailureTimes[$serverIndex] >= 60 ) {
unset( $this->connFailureTimes[$serverIndex] );
unset( $this->connFailureErrors[$serverIndex] );
} else {
return;
}
}
- $now = time();
$this->logger->info( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) );
$this->connFailureTimes[$serverIndex] = $now;
$this->connFailureErrors[$serverIndex] = $exception;
$key4 = $this->cache->makeKey( 'test-key4' );
// cleanup
- $this->cache->delete( $key1 );
- $this->cache->delete( $key2 );
- $this->cache->delete( $key3 );
- $this->cache->delete( $key4 );
+ $this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], 30 );
$this->assertFalse( $ok, "No keys found" );
$this->assertFalse( $this->cache->get( $key3 ) );
$ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
-
$this->assertTrue( $ok, "setMulti() succeeded" );
$this->assertEquals(
3,
$this->assertEquals( 2, $this->cache->get( $key2 ) );
$this->assertEquals( 3, $this->cache->get( $key3 ) );
- $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], $now + 86400 );
- $this->assertTrue( $ok, "Expiry set for all keys" );
-
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3, $key4 ], 300 );
$this->assertFalse( $ok, "One key missing" );
+ $this->assertEquals( 1, $this->cache->get( $key1 ), "Key still live" );
+
+ $now = microtime( true ); // real time
+ $ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
+ $this->assertTrue( $ok, "setMulti() succeeded" );
+
+ $ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], $now + 86400 );
+ $this->assertTrue( $ok, "Expiry set for all keys" );
+ $this->assertEquals( 1, $this->cache->get( $key1 ), "Key still live" );
$this->assertEquals( 2, $this->cache->incr( $key1 ) );
$this->assertEquals( 3, $this->cache->incr( $key2 ) );
$this->assertEquals( 4, $this->cache->incr( $key3 ) );
// cleanup
- $this->cache->delete( $key1 );
- $this->cache->delete( $key2 );
- $this->cache->delete( $key3 );
- $this->cache->delete( $key4 );
+ $this->cache->deleteMulti( [ $key1, $key2, $key3, $key4 ] );
}
/**
*/
public function testGetWithSetCallback() {
$now = 1563892142;
- $this->cache->setMockTime( $now );
- $key = $this->cache->makeKey( self::TEST_KEY );
+ $cache = new HashBagOStuff( [] );
+ $cache->setMockTime( $now );
+ $key = $cache->makeKey( self::TEST_KEY );
- $this->assertFalse( $this->cache->get( $key ), "No value" );
+ $this->assertFalse( $cache->get( $key ), "No value" );
- $value = $this->cache->getWithSetCallback(
+ $value = $cache->getWithSetCallback(
$key,
30,
function ( &$ttl ) {
);
$this->assertEquals( 'hello kitty', $value );
- $this->assertEquals( $value, $this->cache->get( $key ), "Value set" );
+ $this->assertEquals( $value, $cache->get( $key ), "Value set" );
$now += 11;
- $this->assertFalse( $this->cache->get( $key ), "Value expired" );
+ $this->assertFalse( $cache->get( $key ), "Value expired" );
}
/**