47 $this->nodeIdFile =
wfTempDir() .
'/mw-' . __CLASS__ .
'-UID-nodeid';
49 if ( is_file( $this->nodeIdFile ) ) {
50 $nodeId = file_get_contents( $this->nodeIdFile );
53 if ( !preg_match(
'/^[0-9a-f]{12}$/i', $nodeId ) ) {
58 $line = substr( $csv, 0, strcspn( $csv,
"\n" ) );
59 $info = str_getcsv(
$line );
60 $nodeId = isset( $info[0] ) ? str_replace(
'-',
'', $info[0] ) :
'';
61 } elseif ( is_executable(
'/sbin/ifconfig' ) ) {
64 preg_match(
'/\s([0-9a-f]{2}(:[0-9a-f]{2}){5})\s/',
66 $nodeId = isset( $m[1] ) ? str_replace(
':',
'', $m[1] ) :
'';
69 if ( !preg_match(
'/^[0-9a-f]{12}$/i', $nodeId ) ) {
71 $nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 );
73 file_put_contents( $this->nodeIdFile, $nodeId );
75 $this->nodeId32 =
wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
79 $this->lockFile88 =
wfTempDir() .
'/mw-' . __CLASS__ .
'-UID-88';
80 $this->lockFile128 =
wfTempDir() .
'/mw-' . __CLASS__ .
'-UID-128';
87 if ( self::$instance ===
null ) {
88 self::$instance =
new self();
110 if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
111 throw new MWException(
"Base must an integer be between 2 and 36" );
114 $time = $gen->getTimestampAndDelay(
'lockFile88', 1, 1024 );
128 $id_bin .= str_pad( decbin( $counter ), 10,
'0', STR_PAD_LEFT );
132 if ( strlen( $id_bin ) !== 88 ) {
133 throw new MWException(
"Detected overflow for millisecond timestamp." );
154 if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
155 throw new MWException(
"Base must be an integer between 2 and 36" );
158 $time = $gen->getTimestampAndDelay(
'lockFile128', 16384, 1048576 );
168 list(
$time, $counter, $clkSeq ) = $info;
172 $id_bin .= str_pad( decbin( $counter ), 20,
'0', STR_PAD_LEFT );
174 $id_bin .= str_pad( decbin( $clkSeq ), 14,
'0', STR_PAD_LEFT );
178 if ( strlen( $id_bin ) !== 128 ) {
179 throw new MWException(
"Detected overflow for millisecond timestamp." );
197 return sprintf(
'%s-%s-%s-%s-%s',
199 substr( $hex, 0, 8 ),
201 substr( $hex, 8, 4 ),
203 '4' . substr( $hex, 12, 3 ),
205 dechex( 0x8 | ( hexdec( $hex[15] ) & 0x3 ) ) . $hex[16] . substr( $hex, 17, 2 ),
207 substr( $hex, 19, 12 )
219 return str_replace(
'-',
'', self::newUUIDv4(
$flags ) );
235 return current( self::newSequentialPerNodeIDs( $bucket, $bits, 1,
$flags ) );
251 return $gen->getSequentialPerNodeIDs( $bucket, $bits,
$count,
$flags );
267 } elseif (
$count > 10000 ) {
268 throw new MWException(
"Number of requested IDs ($count) is too high." );
269 } elseif ( $bits < 16 || $bits > 48 ) {
270 throw new MWException(
"Requested bit size ($bits) is out of range." );
278 if ( (
$flags & self::QUICK_VOLATILE ) && PHP_SAPI !==
'cli' ) {
285 if ( $counter ===
false ) {
294 if ( $counter ===
null ) {
295 $path =
wfTempDir() .
'/mw-' . __CLASS__ .
'-' . rawurlencode( $bucket ) .
'-48';
297 if ( isset( $this->fileHandles[
$path] ) ) {
298 $handle = $this->fileHandles[
$path];
300 $handle = fopen(
$path,
'cb+' );
301 $this->fileHandles[
$path] = $handle ?:
null;
304 if ( $handle ===
false ) {
305 throw new MWException(
"Could not open '{$path}'." );
306 } elseif ( !flock( $handle, LOCK_EX ) ) {
308 throw new MWException(
"Could not acquire '{$path}'." );
312 $counter = floor( trim( fgets( $handle ) ) ) +
$count;
314 ftruncate( $handle, 0 );
316 fwrite( $handle, fmod( $counter, pow( 2, 48 ) ) );
319 flock( $handle, LOCK_UN );
323 $divisor = pow( 2, $bits );
324 $currentId = floor( $counter -
$count );
325 for ( $i = 0; $i <
$count; ++$i ) {
326 $ids[] = fmod( ++$currentId, $divisor );
345 $path = $this->$lockFile;
346 if ( isset( $this->fileHandles[
$path] ) ) {
347 $handle = $this->fileHandles[
$path];
349 $handle = fopen(
$path,
'cb+' );
350 $this->fileHandles[
$path] = $handle ?:
null;
353 if ( $handle ===
false ) {
354 throw new MWException(
"Could not open '{$this->$lockFile}'." );
355 } elseif ( !flock( $handle, LOCK_EX ) ) {
357 throw new MWException(
"Could not acquire '{$this->$lockFile}'." );
361 $data = explode(
' ', fgets( $handle ) );
362 $clockChanged =
false;
363 if ( count( $data ) == 5 ) {
364 $clkSeq = (int)$data[0] % $clockSeqSize;
365 $prevTime =
array( (
int)$data[1], (
int)$data[2] );
366 $offset = (int)$data[4] % $counterSize;
372 $clockChanged =
true;
374 } elseif (
$time == $prevTime ) {
376 $counter = (int)$data[3] % $counterSize;
377 if ( ++$counter >= $counterSize ) {
378 flock( $handle, LOCK_UN );
379 throw new MWException(
"Counter overflow for timestamp value." );
383 $clkSeq = mt_rand( 0, $clockSeqSize - 1 );
385 $offset = mt_rand( 0, $counterSize - 1 );
391 if ( abs( time() -
$time[0] ) >= 2 ) {
394 flock( $handle, LOCK_UN );
395 throw new MWException(
"Process clock is outdated or drifted." );
398 if ( $clockChanged ) {
401 $clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
402 $offset = mt_rand( 0, $counterSize - 1 );
403 trigger_error(
"Clock was set back; sequence number incremented." );
406 ftruncate( $handle, 0 );
408 fwrite( $handle,
"{$clkSeq} {$time[0]} {$time[1]} {$counter} {$offset}" );
411 flock( $handle, LOCK_UN );
413 return array(
$time, ( $counter + $offset ) % $counterSize, $clkSeq );
426 if ( $ct >=
$time ) {
429 }
while ( ( (
$time[0] - $ct[0] ) * 1000 + (
$time[1] - $ct[1] ) ) <= 10 );
440 $ts = 1000 * $sec + $msec;
441 if ( $ts > pow( 2, 52 ) ) {
443 ': sorry, this function doesn\'t work after the year 144680' );
453 list( $msec, $sec ) = explode(
' ', microtime() );
455 return array( (
int)$sec, (
int)( $msec * 1000 ) );
471 foreach ( $this->fileHandles
as $path => $handle ) {
472 if ( $handle !==
null ) {
475 if ( is_file(
$path ) ) {
478 unset( $this->fileHandles[
$path] );
480 if ( is_file( $this->nodeIdFile ) ) {
481 unlink( $this->nodeIdFile );
499 $gen->deleteCacheFiles();
503 array_map(
'fclose', array_filter( $this->fileHandles ) );