+ return $ok;
+ }
+
+ /**
+ * @param IDatabase $db
+ * @param string|int $timestamp
+ * @param callable|null $progressCallback
+ * @param int $limit
+ * @param int $serversDoneCount
+ * @param int &$keysDeletedCount
+ * @throws DBError
+ */
+ private function deleteServerObjectsExpiringBefore(
+ IDatabase $db,
+ $timestamp,
+ $progressCallback,
+ $limit,
+ $serversDoneCount = 0,
+ &$keysDeletedCount = 0
+ ) {
+ $cutoffUnix = wfTimestamp( TS_UNIX, $timestamp );
+ $shardIndexes = range( 0, $this->shards - 1 );
+ shuffle( $shardIndexes );
+
+ foreach ( $shardIndexes as $numShardsDone => $shardIndex ) {
+ $continue = null; // last exptime
+ $lag = null; // purge lag
+ do {
+ $res = $db->select(
+ $this->getTableNameByShard( $shardIndex ),
+ [ 'keyname', 'exptime' ],
+ array_merge(
+ [ 'exptime < ' . $db->addQuotes( $db->timestamp( $cutoffUnix ) ) ],
+ $continue ? [ 'exptime >= ' . $db->addQuotes( $continue ) ] : []
+ ),
+ __METHOD__,
+ [ 'LIMIT' => min( $limit, 100 ), 'ORDER BY' => 'exptime' ]
+ );
+
+ if ( $res->numRows() ) {
+ $row = $res->current();
+ if ( $lag === null ) {
+ $lag = max( $cutoffUnix - wfTimestamp( TS_UNIX, $row->exptime ), 1 );
+ }
+
+ $keys = [];
+ foreach ( $res as $row ) {
+ $keys[] = $row->keyname;
+ $continue = $row->exptime;
+ }
+
+ $db->delete(
+ $this->getTableNameByShard( $shardIndex ),
+ [
+ 'exptime < ' . $db->addQuotes( $db->timestamp( $cutoffUnix ) ),
+ 'keyname' => $keys
+ ],
+ __METHOD__
+ );
+ $keysDeletedCount += $db->affectedRows();
+ }
+
+ if ( is_callable( $progressCallback ) ) {
+ if ( $lag ) {
+ $remainingLag = $cutoffUnix - wfTimestamp( TS_UNIX, $continue );
+ $processedLag = max( $lag - $remainingLag, 0 );
+ $doneRatio = ( $numShardsDone + $processedLag / $lag ) / $this->shards;
+ } else {
+ $doneRatio = 1;
+ }
+
+ $overallRatio = ( $doneRatio / $this->numServers )
+ + ( $serversDoneCount / $this->numServers );
+ call_user_func( $progressCallback, $overallRatio * 100 );
+ }
+ } while ( $res->numRows() && $keysDeletedCount < $limit );
+ }