From: Aaron Schulz Date: Tue, 31 Dec 2013 23:31:26 +0000 (-0800) Subject: Moved HashRing to /libs X-Git-Tag: 1.31.0-rc.0~17346 X-Git-Url: https://git.cyclocoop.org/%7B%24admin_url%7Dmembres/cotisations/rappels.php?a=commitdiff_plain;h=bb519a91dfe64ab635c73e01636436c24c76ffc6;p=lhc%2Fweb%2Fwiklou.git Moved HashRing to /libs Change-Id: I0b74b386f7459f550816f99aa7e00970c3cff4c7 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 09cbe827d3..2ec9907293 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -678,6 +678,7 @@ $wgAutoloadLocalClasses = array( 'CSSJanus_Tokenizer' => 'includes/libs/CSSJanus.php', 'CSSMin' => 'includes/libs/CSSMin.php', 'GenericArrayObject' => 'includes/libs/GenericArrayObject.php', + 'HashRing' => 'includes/libs/HashRing.php', 'HttpStatus' => 'includes/libs/HttpStatus.php', 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php', 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php', @@ -1071,7 +1072,6 @@ $wgAutoloadLocalClasses = array( 'ConfEditorToken' => 'includes/utils/ConfEditor.php', 'DoubleReplacer' => 'includes/utils/StringUtils.php', 'ExplodeIterator' => 'includes/utils/StringUtils.php', - 'HashRing' => 'includes/utils/HashRing.php', 'HashtableReplacer' => 'includes/utils/StringUtils.php', 'IP' => 'includes/utils/IP.php', 'MWCryptRand' => 'includes/utils/MWCryptRand.php', diff --git a/includes/libs/HashRing.php b/includes/libs/HashRing.php new file mode 100644 index 0000000000..6925c7fb6d --- /dev/null +++ b/includes/libs/HashRing.php @@ -0,0 +1,147 @@ + weight) */ + protected $sourceMap = array(); + /** @var Array (location => (start, end)) */ + protected $ring = array(); + + const RING_SIZE = 268435456; // 2^28 + + /** + * @param array $map (location => weight) + */ + public function __construct( array $map ) { + $map = array_filter( $map, function ( $w ) { + return $w > 0; + } ); + if ( !count( $map ) ) { + throw new UnexpectedValueException( "Ring is empty or all weights are zero." ); + } + $this->sourceMap = $map; + // Sort the locations based on the hash of their names + $hashes = array(); + foreach ( $map as $location => $weight ) { + $hashes[$location] = sha1( $location ); + } + uksort( $map, function ( $a, $b ) use ( $hashes ) { + return strcmp( $hashes[$a], $hashes[$b] ); + } ); + // Fit the map to weight-proportionate one with a space of size RING_SIZE + $sum = array_sum( $map ); + $standardMap = array(); + foreach ( $map as $location => $weight ) { + $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE ); + } + // Build a ring of RING_SIZE spots, with each location at a spot in location hash order + $index = 0; + foreach ( $standardMap as $location => $weight ) { + // Location covers half-closed interval [$index,$index + $weight) + $this->ring[$location] = array( $index, $index + $weight ); + $index += $weight; + } + // Make sure the last location covers what is left + end( $this->ring ); + $this->ring[key( $this->ring )][1] = self::RING_SIZE; + } + + /** + * Get the location of an item on the ring + * + * @param string $item + * @return string Location + */ + public function getLocation( $item ) { + $locations = $this->getLocations( $item, 1 ); + + return $locations[0]; + } + + /** + * Get the location of an item on the ring, as well as the next clockwise locations + * + * @param string $item + * @param integer $limit Maximum number of locations to return + * @return array List of locations + */ + public function getLocations( $item, $limit ) { + $locations = array(); + $primaryLocation = null; + $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits + foreach ( $this->ring as $location => $range ) { + if ( count( $locations ) >= $limit ) { + break; + } + // The $primaryLocation is the location the item spot is in. + // After that is reached, keep appending the next locations. + if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) { + if ( $primaryLocation === null ) { + $primaryLocation = $location; + } + $locations[] = $location; + } + } + // If more locations are requested, wrap-around and keep adding them + reset( $this->ring ); + while ( count( $locations ) < $limit ) { + list( $location, ) = each( $this->ring ); + if ( $location === $primaryLocation ) { + break; // don't go in circles + } + $locations[] = $location; + } + + return $locations; + } + + /** + * Get the map of locations to weight (ignores 0-weight items) + * + * @return array + */ + public function getLocationWeights() { + return $this->sourceMap; + } + + /** + * Get a new hash ring with a location removed from the ring + * + * @param string $location + * @return HashRing|bool Returns false if no non-zero weighted spots are left + */ + public function newWithoutLocation( $location ) { + $map = $this->sourceMap; + unset( $map[$location] ); + if ( count( $map ) ) { + return new self( $map ); + } + + return false; + } +} diff --git a/includes/utils/HashRing.php b/includes/utils/HashRing.php deleted file mode 100644 index c152d410b0..0000000000 --- a/includes/utils/HashRing.php +++ /dev/null @@ -1,147 +0,0 @@ - weight) */ - protected $sourceMap = array(); - /** @var Array (location => (start, end)) */ - protected $ring = array(); - - const RING_SIZE = 268435456; // 2^28 - - /** - * @param array $map (location => weight) - */ - public function __construct( array $map ) { - $map = array_filter( $map, function ( $w ) { - return $w > 0; - } ); - if ( !count( $map ) ) { - throw new MWException( "Ring is empty or all weights are zero." ); - } - $this->sourceMap = $map; - // Sort the locations based on the hash of their names - $hashes = array(); - foreach ( $map as $location => $weight ) { - $hashes[$location] = sha1( $location ); - } - uksort( $map, function ( $a, $b ) use ( $hashes ) { - return strcmp( $hashes[$a], $hashes[$b] ); - } ); - // Fit the map to weight-proportionate one with a space of size RING_SIZE - $sum = array_sum( $map ); - $standardMap = array(); - foreach ( $map as $location => $weight ) { - $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE ); - } - // Build a ring of RING_SIZE spots, with each location at a spot in location hash order - $index = 0; - foreach ( $standardMap as $location => $weight ) { - // Location covers half-closed interval [$index,$index + $weight) - $this->ring[$location] = array( $index, $index + $weight ); - $index += $weight; - } - // Make sure the last location covers what is left - end( $this->ring ); - $this->ring[key( $this->ring )][1] = self::RING_SIZE; - } - - /** - * Get the location of an item on the ring - * - * @param string $item - * @return string Location - */ - public function getLocation( $item ) { - $locations = $this->getLocations( $item, 1 ); - - return $locations[0]; - } - - /** - * Get the location of an item on the ring, as well as the next clockwise locations - * - * @param string $item - * @param integer $limit Maximum number of locations to return - * @return array List of locations - */ - public function getLocations( $item, $limit ) { - $locations = array(); - $primaryLocation = null; - $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits - foreach ( $this->ring as $location => $range ) { - if ( count( $locations ) >= $limit ) { - break; - } - // The $primaryLocation is the location the item spot is in. - // After that is reached, keep appending the next locations. - if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) { - if ( $primaryLocation === null ) { - $primaryLocation = $location; - } - $locations[] = $location; - } - } - // If more locations are requested, wrap-around and keep adding them - reset( $this->ring ); - while ( count( $locations ) < $limit ) { - list( $location, ) = each( $this->ring ); - if ( $location === $primaryLocation ) { - break; // don't go in circles - } - $locations[] = $location; - } - - return $locations; - } - - /** - * Get the map of locations to weight (ignores 0-weight items) - * - * @return array - */ - public function getLocationWeights() { - return $this->sourceMap; - } - - /** - * Get a new hash ring with a location removed from the ring - * - * @param string $location - * @return HashRing|bool Returns false if no non-zero weighted spots are left - */ - public function newWithoutLocation( $location ) { - $map = $this->sourceMap; - unset( $map[$location] ); - if ( count( $map ) ) { - return new self( $map ); - } - - return false; - } -} diff --git a/tests/phpunit/includes/libs/HashRingTest.php b/tests/phpunit/includes/libs/HashRingTest.php new file mode 100644 index 0000000000..68dfea1f7f --- /dev/null +++ b/tests/phpunit/includes/libs/HashRingTest.php @@ -0,0 +1,56 @@ + 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) ); + + $locations = array(); + for ( $i = 0; $i < 20; $i++ ) { + $locations[ "hello$i"] = $ring->getLocation( "hello$i" ); + } + $expectedLocations = array( + "hello0" => "s5", + "hello1" => "s6", + "hello2" => "s2", + "hello3" => "s5", + "hello4" => "s6", + "hello5" => "s4", + "hello6" => "s5", + "hello7" => "s4", + "hello8" => "s5", + "hello9" => "s5", + "hello10" => "s3", + "hello11" => "s6", + "hello12" => "s1", + "hello13" => "s3", + "hello14" => "s3", + "hello15" => "s5", + "hello16" => "s4", + "hello17" => "s6", + "hello18" => "s6", + "hello19" => "s3" + ); + + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + + $locations = array(); + for ( $i = 0; $i < 5; $i++ ) { + $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 ); + } + + $expectedLocations = array( + "hello0" => array( "s5", "s6" ), + "hello1" => array( "s6", "s4" ), + "hello2" => array( "s2", "s1" ), + "hello3" => array( "s5", "s6" ), + "hello4" => array( "s6", "s4" ), + ); + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + } +} diff --git a/tests/phpunit/includes/utils/HashRingTest.php b/tests/phpunit/includes/utils/HashRingTest.php deleted file mode 100644 index 68dfea1f7f..0000000000 --- a/tests/phpunit/includes/utils/HashRingTest.php +++ /dev/null @@ -1,56 +0,0 @@ - 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) ); - - $locations = array(); - for ( $i = 0; $i < 20; $i++ ) { - $locations[ "hello$i"] = $ring->getLocation( "hello$i" ); - } - $expectedLocations = array( - "hello0" => "s5", - "hello1" => "s6", - "hello2" => "s2", - "hello3" => "s5", - "hello4" => "s6", - "hello5" => "s4", - "hello6" => "s5", - "hello7" => "s4", - "hello8" => "s5", - "hello9" => "s5", - "hello10" => "s3", - "hello11" => "s6", - "hello12" => "s1", - "hello13" => "s3", - "hello14" => "s3", - "hello15" => "s5", - "hello16" => "s4", - "hello17" => "s6", - "hello18" => "s6", - "hello19" => "s3" - ); - - $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); - - $locations = array(); - for ( $i = 0; $i < 5; $i++ ) { - $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 ); - } - - $expectedLocations = array( - "hello0" => array( "s5", "s6" ), - "hello1" => array( "s6", "s4" ), - "hello2" => array( "s2", "s1" ), - "hello3" => array( "s5", "s6" ), - "hello4" => array( "s6", "s4" ), - ); - $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); - } -}