X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Futils%2FUIDGenerator.php;h=e60293b803171c87d911d61e005bc37c3da73311;hb=96603cd221051a324c902b6d4debd88ff1418404;hp=10ff957bca9dc1dc7a2fceb33caa9cb76a6a37d9;hpb=3264a2f0cbfe8287d9602a5a4fcd8ce52b90474e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php index 10ff957bca..e60293b803 100644 --- a/includes/utils/UIDGenerator.php +++ b/includes/utils/UIDGenerator.php @@ -40,6 +40,7 @@ class UIDGenerator { protected $fileHandles = array(); // cache file handles const QUICK_RAND = 1; // get randomness from fast and insecure sources + const QUICK_VOLATILE = 2; // use an APC like in-memory counter if available protected function __construct() { $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid'; @@ -214,6 +215,116 @@ class UIDGenerator { return str_replace( '-', '', self::newUUIDv4( $flags ) ); } + /** + * Return an ID that is sequential *only* for this node and bucket + * + * These IDs are suitable for per-host sequence numbers, e.g. for some packet protocols. + * If UIDGenerator::QUICK_VOLATILE is used the counter might reset on server restart. + * + * @param string $bucket Arbitrary bucket name (should be ASCII) + * @param integer $bits Bit size (<=48) of resulting numbers before wrap-around + * @param integer $flags (supports UIDGenerator::QUICK_VOLATILE) + * @return float Integer value as float + * @since 1.23 + */ + public static function newSequentialPerNodeID( $bucket, $bits = 48, $flags = 0 ) { + return current( self::newSequentialPerNodeIDs( $bucket, $bits, 1, $flags ) ); + } + + /** + * Return IDs that are sequential *only* for this node and bucket + * + * @see UIDGenerator::newSequentialPerNodeID() + * @param string $bucket Arbitrary bucket name (should be ASCII) + * @param integer $bits Bit size (16 to 48) of resulting numbers before wrap-around + * @param integer $count Number of IDs to return (1 to 10000) + * @param integer $flags (supports UIDGenerator::QUICK_VOLATILE) + * @return array Ordered list of float integer values + * @since 1.23 + */ + public static function newSequentialPerNodeIDs( $bucket, $bits, $count, $flags = 0 ) { + $gen = self::singleton(); + return $gen->getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ); + } + + /** + * Return IDs that are sequential *only* for this node and bucket + * + * @see UIDGenerator::newSequentialPerNodeID() + * @param string $bucket Arbitrary bucket name (should be ASCII) + * @param integer $bits Bit size (16 to 48) of resulting numbers before wrap-around + * @param integer $count Number of IDs to return (1 to 10000) + * @param integer $flags (supports UIDGenerator::QUICK_VOLATILE) + * @return array Ordered list of float integer values + */ + protected function getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ) { + if ( $count <= 0 ) { + return array(); // nothing to do + } elseif ( $count > 10000 ) { + throw new MWException( "Number of requested IDs ($count) is too high." ); + } elseif ( $bits < 16 || $bits > 48 ) { + throw new MWException( "Requested bit size ($bits) is out of range." ); + } + + $counter = null; // post-increment persistent counter value + + // Use APC/eAccelerator/xcache if requested, available, and not in CLI mode; + // Counter values would not survive accross script instances in CLI mode. + $cache = null; + if ( ( $flags & self::QUICK_VOLATILE ) && PHP_SAPI !== 'cli' ) { + try { + $cache = ObjectCache::newAccelerator( array() ); + } catch ( MWException $e ) {} // not supported + } + if ( $cache ) { + $counter = $cache->incr( $bucket, $count ); + if ( $counter === false ) { + if ( !$cache->add( $bucket, $count ) ) { + throw new MWException( 'Unable to set value to ' . get_class( $cache ) ); + } + $counter = $count; + } + } + + // Note: use of fmod() avoids "division by zero" on 32 bit machines + if ( $counter === null ) { + $path = wfTempDir() . '/mw-' . __CLASS__ . '-' . rawurlencode( $bucket ) . '-48'; + // Get the UID lock file handle + if ( isset( $this->fileHandles[$path] ) ) { + $handle = $this->fileHandles[$path]; + } else { + $handle = fopen( $path, 'cb+' ); + $this->fileHandles[$path] = $handle ?: null; // cache + } + // Acquire the UID lock file + if ( $handle === false ) { + throw new MWException( "Could not open '{$path}'." ); + } elseif ( !flock( $handle, LOCK_EX ) ) { + fclose( $handle ); + throw new MWException( "Could not acquire '{$path}'." ); + } + // Fetch the counter value and increment it... + rewind( $handle ); + $counter = floor( trim( fgets( $handle ) ) ) + $count; // fetch as float + // Write back the new counter value + ftruncate( $handle, 0 ); + rewind( $handle ); + fwrite( $handle, fmod( $counter, pow( 2, 48 ) ) ); // warp-around as needed + fflush( $handle ); + // Release the UID lock file + flock( $handle, LOCK_UN ); + } + + $ids = array(); + $divisor = pow( 2, $bits ); + $currentId = floor( $counter - $count ); // pre-increment counter value + for ( $i = 0; $i < $count; ++$i ) { + $ids[] = fmod( ++$currentId, $divisor ); + } + + return $ids; + } + /** * Get a (time,counter,clock sequence) where (time,counter) is higher * than any previous (time,counter) value for the given clock sequence. @@ -237,6 +348,7 @@ class UIDGenerator { if ( $handle === false ) { throw new MWException( "Could not open '{$this->$lockFile}'." ); } elseif ( !flock( $handle, LOCK_EX ) ) { + fclose( $handle ); throw new MWException( "Could not acquire '{$this->$lockFile}'." ); } // Get the current timestamp, clock sequence number, last time, and counter