MediaWiki fundraising/REL1_35
HashRing.php
Go to the documentation of this file.
1<?php
44class HashRing implements Serializable {
46 protected $algo;
51
53 protected $baseRing;
55 protected $liveRing;
56
58 private const HASHES_PER_LOCATION = 40;
60 private const SECTORS_PER_HASH = 4;
61
62 public const KEY_POS = 0;
63 public const KEY_LOCATION = 1;
64
66 public const RING_ALL = 0;
68 public const RING_LIVE = 1;
69
78 public function __construct( array $map, $algo = 'sha1', array $ejections = [] ) {
79 $this->init( $map, $algo, $ejections );
80 }
81
87 protected function init( array $map, $algo, array $ejections ) {
88 if ( !in_array( $algo, hash_algos(), true ) ) {
89 throw new RuntimeException( __METHOD__ . ": unsupported '$algo' hash algorithm." );
90 }
91
92 $weightByLocation = array_filter( $map );
93 if ( $weightByLocation === [] ) {
94 throw new UnexpectedValueException( "No locations with non-zero weight." );
95 } elseif ( min( $map ) < 0 ) {
96 throw new InvalidArgumentException( "Location weight cannot be negative." );
97 }
98
99 $this->algo = $algo;
100 $this->weightByLocation = $weightByLocation;
101 $this->ejectExpiryByLocation = $ejections;
102 $this->baseRing = $this->buildLocationRing( $this->weightByLocation );
103 }
104
112 final public function getLocation( $item ) {
113 return $this->getLocations( $item, 1 )[0];
114 }
115
126 public function getLocations( $item, $limit, $from = self::RING_ALL ) {
127 if ( $from === self::RING_ALL ) {
128 $ring = $this->baseRing;
129 } elseif ( $from === self::RING_LIVE ) {
130 $ring = $this->getLiveRing();
131 } else {
132 throw new InvalidArgumentException( "Invalid ring source specified." );
133 }
134
135 // Short-circuit for the common single-location case. Note that if there was only one
136 // location and it was ejected from the live ring, getLiveRing() would have error out.
137 if ( count( $this->weightByLocation ) == 1 ) {
138 return ( $limit > 0 ) ? [ $ring[0][self::KEY_LOCATION] ] : [];
139 }
140
141 // Locate the node index for this item's position on the hash ring
142 $itemIndex = $this->findNodeIndexForPosition( $this->getItemPosition( $item ), $ring );
143
144 $locations = [];
145 $currentIndex = null;
146 while ( count( $locations ) < $limit ) {
147 if ( $currentIndex === null ) {
148 $currentIndex = $itemIndex;
149 } else {
150 $currentIndex = $this->getNextClockwiseNodeIndex( $currentIndex, $ring );
151 if ( $currentIndex === $itemIndex ) {
152 break; // all nodes visited
153 }
154 }
155 $nodeLocation = $ring[$currentIndex][self::KEY_LOCATION];
156 if ( !in_array( $nodeLocation, $locations, true ) ) {
157 // Ignore other nodes for the same locations already added
158 $locations[] = $nodeLocation;
159 }
160 }
161
162 return $locations;
163 }
164
170 private function findNodeIndexForPosition( $position, $ring ) {
171 $count = count( $ring );
172 if ( $count === 0 ) {
173 return null;
174 }
175
176 $index = null;
177 $lowPos = 0;
178 $highPos = $count;
179 while ( true ) {
180 $midPos = (int)( ( $lowPos + $highPos ) / 2 );
181 if ( $midPos === $count ) {
182 $index = 0;
183 break;
184 }
185
186 $midVal = $ring[$midPos][self::KEY_POS];
187 $midMinusOneVal = ( $midPos === 0 ) ? 0 : $ring[$midPos - 1][self::KEY_POS];
188 if ( $position <= $midVal && $position > $midMinusOneVal ) {
189 $index = $midPos;
190 break;
191 }
192
193 if ( $midVal < $position ) {
194 $lowPos = $midPos + 1;
195 } else {
196 $highPos = $midPos - 1;
197 }
198
199 if ( $lowPos > $highPos ) {
200 $index = 0;
201 break;
202 }
203 }
204
205 return $index;
206 }
207
213 public function getLocationWeights() {
215 }
216
225 public function ejectFromLiveRing( $location, $ttl ) {
226 if ( !isset( $this->weightByLocation[$location] ) ) {
227 throw new UnexpectedValueException( "No location '$location' in the ring." );
228 }
229
230 $expiry = $this->getCurrentTime() + $ttl;
231 $this->ejectExpiryByLocation[$location] = $expiry;
232
233 $this->liveRing = null; // invalidate ring cache
234
235 return ( count( $this->ejectExpiryByLocation ) < count( $this->weightByLocation ) );
236 }
237
245 final public function getLiveLocation( $item ) {
246 return $this->getLocations( $item, 1, self::RING_LIVE )[0];
247 }
248
257 final public function getLiveLocations( $item, $limit ) {
258 return $this->getLocations( $item, $limit, self::RING_LIVE );
259 }
260
267 public function getLiveLocationWeights() {
268 $now = $this->getCurrentTime();
269
270 return array_diff_key(
271 $this->weightByLocation,
272 array_filter(
273 $this->ejectExpiryByLocation,
274 function ( $expiry ) use ( $now ) {
275 return ( $expiry > $now );
276 }
277 )
278 );
279 }
280
285 private function buildLocationRing( array $weightByLocation ) {
286 $locationCount = count( $weightByLocation );
287 $totalWeight = array_sum( $weightByLocation );
288
289 $ring = [];
290 // Assign nodes to all locations based on location weight
291 $claimed = []; // (position as string => (node, index))
292 foreach ( $weightByLocation as $location => $weight ) {
293 $ratio = $weight / $totalWeight;
294 // There $locationCount * (HASHES_PER_LOCATION * 4) nodes available;
295 // assign a few groups of nodes to this location based on its weight.
296 $nodesQuartets = intval( $ratio * self::HASHES_PER_LOCATION * $locationCount );
297 for ( $qi = 0; $qi < $nodesQuartets; ++$qi ) {
298 // For efficiency, get 4 points per hash call and 4X node count.
299 // If $algo is MD5, then this matches that of with libketama.
300 // See https://github.com/RJ/ketama/blob/master/libketama/ketama.c
301 $positions = $this->getNodePositionQuartet( "{$location}-{$qi}" );
302 foreach ( $positions as $gi => $position ) {
303 $node = ( $qi * self::SECTORS_PER_HASH + $gi ) . "@$location";
304 $posKey = (string)$position; // large integer
305 if ( isset( $claimed[$posKey] ) ) {
306 // Disallow duplicates for sanity (name decides precedence)
307 if ( $claimed[$posKey]['node'] > $node ) {
308 continue;
309 } else {
310 unset( $ring[$claimed[$posKey]['index']] );
311 }
312 }
313 $ring[] = [
314 self::KEY_POS => $position,
315 self::KEY_LOCATION => $location
316 ];
317 $claimed[$posKey] = [ 'node' => $node, 'index' => count( $ring ) - 1 ];
318 }
319 }
320 }
321 // Sort the locations into clockwise order based on the hash ring position
322 usort( $ring, function ( $a, $b ) {
323 if ( $a[self::KEY_POS] === $b[self::KEY_POS] ) {
324 throw new UnexpectedValueException( 'Duplicate node positions.' );
325 }
326
327 return ( $a[self::KEY_POS] < $b[self::KEY_POS] ? -1 : 1 );
328 } );
329
330 return $ring;
331 }
332
337 private function getItemPosition( $item ) {
338 // If $algo is MD5, then this matches that of with libketama.
339 // See https://github.com/RJ/ketama/blob/master/libketama/ketama.c
340 $octets = substr( hash( $this->algo, (string)$item, true ), 0, 4 );
341 if ( strlen( $octets ) != 4 ) {
342 throw new UnexpectedValueException( __METHOD__ . ": {$this->algo} is < 32 bits." );
343 }
344
345 $pos = unpack( 'V', $octets )[1];
346 if ( $pos < 0 ) {
347 // Most-significant-bit is set, causing unpack() to return a negative integer due
348 // to the fact that it returns a signed int. Cast it to an unsigned integer string.
349 $pos = sprintf( '%u', $pos );
350 }
351
352 return (float)$pos;
353 }
354
359 private function getNodePositionQuartet( $nodeGroupName ) {
360 $octets = substr( hash( $this->algo, (string)$nodeGroupName, true ), 0, 16 );
361 if ( strlen( $octets ) != 16 ) {
362 throw new UnexpectedValueException( __METHOD__ . ": {$this->algo} is < 128 bits." );
363 }
364
365 $positions = [];
366 foreach ( unpack( 'V4', $octets ) as $signed ) {
367 $positions[] = (float)sprintf( '%u', $signed );
368 }
369
370 return $positions;
371 }
372
378 private function getNextClockwiseNodeIndex( $i, $ring ) {
379 if ( !isset( $ring[$i] ) ) {
380 throw new UnexpectedValueException( __METHOD__ . ": reference index is invalid." );
381 }
382
383 $next = $i + 1;
384
385 return ( $next < count( $ring ) ) ? $next : 0;
386 }
387
394 protected function getLiveRing() {
395 if ( !$this->ejectExpiryByLocation ) {
396 return $this->baseRing; // nothing ejected
397 }
398
399 $now = $this->getCurrentTime();
400
401 if ( $this->liveRing === null || min( $this->ejectExpiryByLocation ) <= $now ) {
402 // Live ring needs to be regerenated...
403 $this->ejectExpiryByLocation = array_filter(
404 $this->ejectExpiryByLocation,
405 function ( $expiry ) use ( $now ) {
406 return ( $expiry > $now );
407 }
408 );
409
410 if ( count( $this->ejectExpiryByLocation ) ) {
411 // Some locations are still ejected from the ring
412 $liveRing = [];
413 foreach ( $this->baseRing as $i => $nodeInfo ) {
414 $location = $nodeInfo[self::KEY_LOCATION];
415 if ( !isset( $this->ejectExpiryByLocation[$location] ) ) {
416 $liveRing[] = $nodeInfo;
417 }
418 }
419 } else {
421 }
422
423 $this->liveRing = $liveRing;
424 }
425
426 if ( !$this->liveRing ) {
427 throw new UnexpectedValueException( "The live ring is currently empty." );
428 }
429
430 return $this->liveRing;
431 }
432
436 protected function getCurrentTime() {
437 return time();
438 }
439
440 public function serialize(): string {
441 return serialize( $this->__serialize() );
442 }
443
444 public function __serialize() {
445 return [
446 'algorithm' => $this->algo,
447 'locations' => $this->weightByLocation,
448 'ejections' => $this->ejectExpiryByLocation
449 ];
450 }
451
452 public function unserialize( $serialized ): void {
454 }
455
456 public function __unserialize( $data ) {
457 if ( is_array( $data ) ) {
458 $this->init( $data['locations'], $data['algorithm'], $data['ejections'] );
459 } else {
460 throw new UnexpectedValueException( __METHOD__ . ": unable to decode JSON." );
461 }
462 }
463}
__unserialize( $data)
unserialize( $serialized)
return[ 'abap'=> true, 'abl'=> true, 'abnf'=> true, 'aconf'=> true, 'actionscript'=> true, 'actionscript3'=> true, 'ada'=> true, 'ada2005'=> true, 'ada95'=> true, 'adl'=> true, 'agda'=> true, 'aheui'=> true, 'ahk'=> true, 'alloy'=> true, 'ambienttalk'=> true, 'ambienttalk/2'=> true, 'ampl'=> true, 'antlr'=> true, 'antlr-actionscript'=> true, 'antlr-as'=> true, 'antlr-c#'=> true, 'antlr-cpp'=> true, 'antlr-csharp'=> true, 'antlr-java'=> true, 'antlr-objc'=> true, 'antlr-perl'=> true, 'antlr-python'=> true, 'antlr-rb'=> true, 'antlr-ruby'=> true, 'apache'=> true, 'apacheconf'=> true, 'apl'=> true, 'applescript'=> true, 'arduino'=> true, 'arexx'=> true, 'arrow'=> true, 'as'=> true, 'as3'=> true, 'asm'=> true, 'aspectj'=> true, 'aspx-cs'=> true, 'aspx-vb'=> true, 'asy'=> true, 'asymptote'=> true, 'at'=> true, 'augeas'=> true, 'autohotkey'=> true, 'autoit'=> true, 'awk'=> true, 'b3d'=> true, 'bare'=> true, 'basemake'=> true, 'bash'=> true, 'basic'=> true, 'bat'=> true, 'batch'=> true, 'bbcbasic'=> true, 'bbcode'=> true, 'bc'=> true, 'befunge'=> true, 'bf'=> true, 'bib'=> true, 'bibtex'=> true, 'blitzbasic'=> true, 'blitzmax'=> true, 'bmax'=> true, 'bnf'=> true, 'boa'=> true, 'boo'=> true, 'boogie'=> true, 'bplus'=> true, 'brainfuck'=> true, 'bro'=> true, 'bsdmake'=> true, 'bst'=> true, 'bst-pybtex'=> true, 'bugs'=> true, 'c'=> true, 'c#'=> true, 'c++'=> true, 'c++-objdumb'=> true, 'c-objdump'=> true, 'ca65'=> true, 'cadl'=> true, 'camkes'=> true, 'capdl'=> true, 'capnp'=> true, 'cbmbas'=> true, 'ceylon'=> true, 'cf3'=> true, 'cfc'=> true, 'cfengine3'=> true, 'cfg'=> true, 'cfm'=> true, 'cfs'=> true, 'chai'=> true, 'chaiscript'=> true, 'chapel'=> true, 'charmci'=> true, 'cheetah'=> true, 'chpl'=> true, 'cirru'=> true, 'cl'=> true, 'clay'=> true, 'clean'=> true, 'clipper'=> true, 'clj'=> true, 'cljs'=> true, 'clojure'=> true, 'clojurescript'=> true, 'cmake'=> true, 'cobol'=> true, 'cobolfree'=> true, 'coffee'=> true, 'coffee-script'=> true, 'coffeescript'=> true, 'common-lisp'=> true, 'componentpascal'=> true, 'console'=> true, 'control'=> true, 'coq'=> true, 'cp'=> true, 'cpp'=> true, 'cpp-objdump'=> true, 'cpsa'=> true, 'cr'=> true, 'crmsh'=> true, 'croc'=> true, 'cry'=> true, 'cryptol'=> true, 'crystal'=> true, 'csh'=> true, 'csharp'=> true, 'csound'=> true, 'csound-csd'=> true, 'csound-document'=> true, 'csound-orc'=> true, 'csound-sco'=> true, 'csound-score'=> true, 'css'=> true, 'css+django'=> true, 'css+erb'=> true, 'css+genshi'=> true, 'css+genshitext'=> true, 'css+jinja'=> true, 'css+lasso'=> true, 'css+mako'=> true, 'css+mozpreproc'=> true, 'css+myghty'=> true, 'css+php'=> true, 'css+ruby'=> true, 'css+smarty'=> true, 'cu'=> true, 'cucumber'=> true, 'cuda'=> true, 'cxx-objdump'=> true, 'cypher'=> true, 'cython'=> true, 'd'=> true, 'd-objdump'=> true, 'dart'=> true, 'dasm16'=> true, 'debcontrol'=> true, 'debsources'=> true, 'delphi'=> true, 'devicetree'=> true, 'dg'=> true, 'diff'=> true, 'django'=> true, 'dmesg'=> true, 'do'=> true, 'docker'=> true, 'dockerfile'=> true, 'dosbatch'=> true, 'doscon'=> true, 'dosini'=> true, 'dpatch'=> true, 'dtd'=> true, 'dts'=> true, 'duby'=> true, 'duel'=> true, 'dylan'=> true, 'dylan-console'=> true, 'dylan-lid'=> true, 'dylan-repl'=> true, 'earl-grey'=> true, 'earlgrey'=> true, 'easytrieve'=> true, 'ebnf'=> true, 'ec'=> true, 'ecl'=> true, 'eg'=> true, 'eiffel'=> true, 'elisp'=> true, 'elixir'=> true, 'elm'=> true, 'emacs'=> true, 'emacs-lisp'=> true, 'email'=> true, 'eml'=> true, 'erb'=> true, 'erl'=> true, 'erlang'=> true, 'evoque'=> true, 'ex'=> true, 'execline'=> true, 'exs'=> true, 'extempore'=> true, 'ezhil'=> true, 'f#'=> true, 'factor'=> true, 'fan'=> true, 'fancy'=> true, 'felix'=> true, 'fennel'=> true, 'fish'=> true, 'fishshell'=> true, 'flatline'=> true, 'flo'=> true, 'floscript'=> true, 'flx'=> true, 'fnl'=> true, 'forth'=> true, 'fortran'=> true, 'fortranfixed'=> true, 'foxpro'=> true, 'freefem'=> true, 'fsharp'=> true, 'fstar'=> true, 'fy'=> true, 'gap'=> true, 'gas'=> true, 'gawk'=> true, 'gd'=> true, 'gdscript'=> true, 'genshi'=> true, 'genshitext'=> true, 'gherkin'=> true, 'glsl'=> true, 'gnuplot'=> true, 'go'=> true, 'golo'=> true, 'gooddata-cl'=> true, 'gosu'=> true, 'groff'=> true, 'groovy'=> true, 'gst'=> true, 'haml'=> true, 'handlebars'=> true, 'haskell'=> true, 'haxe'=> true, 'haxeml'=> true, 'hexdump'=> true, 'hlsl'=> true, 'hs'=> true, 'hsa'=> true, 'hsail'=> true, 'hspec'=> true, 'html'=> true, 'html+cheetah'=> true, 'html+django'=> true, 'html+erb'=> true, 'html+evoque'=> true, 'html+genshi'=> true, 'html+handlebars'=> true, 'html+jinja'=> true, 'html+kid'=> true, 'html+lasso'=> true, 'html+mako'=> true, 'html+myghty'=> true, 'html+ng2'=> true, 'html+php'=> true, 'html+ruby'=> true, 'html+smarty'=> true, 'html+spitfire'=> true, 'html+twig'=> true, 'html+velocity'=> true, 'htmlcheetah'=> true, 'htmldjango'=> true, 'http'=> true, 'hx'=> true, 'hxml'=> true, 'hxsl'=> true, 'hy'=> true, 'hybris'=> true, 'hylang'=> true, 'i6'=> true, 'i6t'=> true, 'i7'=> true, 'icon'=> true, 'idl'=> true, 'idl4'=> true, 'idr'=> true, 'idris'=> true, 'iex'=> true, 'igor'=> true, 'igorpro'=> true, 'ik'=> true, 'inform6'=> true, 'inform7'=> true, 'ini'=> true, 'io'=> true, 'ioke'=> true, 'irb'=> true, 'irc'=> true, 'isabelle'=> true, 'j'=> true, 'jade'=> true, 'jags'=> true, 'jasmin'=> true, 'jasminxt'=> true, 'java'=> true, 'javascript'=> true, 'javascript+cheetah'=> true, 'javascript+django'=> true, 'javascript+erb'=> true, 'javascript+genshi'=> true, 'javascript+genshitext'=> true, 'javascript+jinja'=> true, 'javascript+lasso'=> true, 'javascript+mako'=> true, 'javascript+mozpreproc'=> true, 'javascript+myghty'=> true, 'javascript+php'=> true, 'javascript+ruby'=> true, 'javascript+smarty'=> true, 'javascript+spitfire'=> true, 'jbst'=> true, 'jcl'=> true, 'jinja'=> true, 'jl'=> true, 'jlcon'=> true, 'jproperties'=> true, 'js'=> true, 'js+cheetah'=> true, 'js+django'=> true, 'js+erb'=> true, 'js+genshi'=> true, 'js+genshitext'=> true, 'js+jinja'=> true, 'js+lasso'=> true, 'js+mako'=> true, 'js+myghty'=> true, 'js+php'=> true, 'js+ruby'=> true, 'js+smarty'=> true, 'js+spitfire'=> true, 'jsgf'=> true, 'json'=> true, 'json-ld'=> true, 'json-object'=> true, 'jsonld'=> true, 'jsonml+bst'=> true, 'jsp'=> true, 'julia'=> true, 'juttle'=> true, 'kal'=> true, 'kconfig'=> true, 'kernel-config'=> true, 'kid'=> true, 'kmsg'=> true, 'koka'=> true, 'kotlin'=> true, 'ksh'=> true, 'lagda'=> true, 'lasso'=> true, 'lassoscript'=> true, 'latex'=> true, 'lcry'=> true, 'lcryptol'=> true, 'lean'=> true, 'less'=> true, 'lhaskell'=> true, 'lhs'=> true, 'lid'=> true, 'lidr'=> true, 'lidris'=> true, 'lighttpd'=> true, 'lighty'=> true, 'limbo'=> true, 'linux-config'=> true, 'liquid'=> true, 'lisp'=> true, 'literate-agda'=> true, 'literate-cryptol'=> true, 'literate-haskell'=> true, 'literate-idris'=> true, 'live-script'=> true, 'livescript'=> true, 'llvm'=> true, 'llvm-mir'=> true, 'llvm-mir-body'=> true, 'logos'=> true, 'logtalk'=> true, 'lsl'=> true, 'lua'=> true, 'm2'=> true, 'make'=> true, 'makefile'=> true, 'mako'=> true, 'man'=> true, 'maql'=> true, 'mask'=> true, 'mason'=> true, 'mathematica'=> true, 'matlab'=> true, 'matlabsession'=> true, 'mawk'=> true, 'md'=> true, 'menuconfig'=> true, 'mf'=> true, 'mime'=> true, 'minid'=> true, 'miniscript'=> true, 'mma'=> true, 'modelica'=> true, 'modula2'=> true, 'moin'=> true, 'monkey'=> true, 'monte'=> true, 'moo'=> true, 'moocode'=> true, 'moon'=> true, 'moonscript'=> true, 'mosel'=> true, 'mozhashpreproc'=> true, 'mozpercentpreproc'=> true, 'mq4'=> true, 'mq5'=> true, 'mql'=> true, 'mql4'=> true, 'mql5'=> true, 'ms'=> true, 'msc'=> true, 'mscgen'=> true, 'mupad'=> true, 'mxml'=> true, 'myghty'=> true, 'mysql'=> true, 'nasm'=> true, 'nawk'=> true, 'nb'=> true, 'ncl'=> true, 'nemerle'=> true, 'nesc'=> true, 'newlisp'=> true, 'newspeak'=> true, 'ng2'=> true, 'nginx'=> true, 'nim'=> true, 'nimrod'=> true, 'nit'=> true, 'nix'=> true, 'nixos'=> true, 'notmuch'=> true, 'nroff'=> true, 'nsh'=> true, 'nsi'=> true, 'nsis'=> true, 'numpy'=> true, 'nusmv'=> true, 'obj-c'=> true, 'obj-c++'=> true, 'obj-j'=> true, 'objc'=> true, 'objc++'=> true, 'objdump'=> true, 'objdump-nasm'=> true, 'objective-c'=> true, 'objective-c++'=> true, 'objective-j'=> true, 'objectivec'=> true, 'objectivec++'=> true, 'objectivej'=> true, 'objectpascal'=> true, 'objj'=> true, 'ocaml'=> true, 'octave'=> true, 'odin'=> true, 'ooc'=> true, 'opa'=> true, 'openbugs'=> true, 'openedge'=> true, 'pacmanconf'=> true, 'pan'=> true, 'parasail'=> true, 'pas'=> true, 'pascal'=> true, 'pawn'=> true, 'pcmk'=> true, 'peg'=> true, 'perl'=> true, 'perl6'=> true, 'php'=> true, 'php3'=> true, 'php4'=> true, 'php5'=> true, 'pig'=> true, 'pike'=> true, 'pkgconfig'=> true, 'pl'=> true, 'pl6'=> true, 'plpgsql'=> true, 'po'=> true, 'pointless'=> true, 'pony'=> true, 'posh'=> true, 'postgres'=> true, 'postgres-console'=> true, 'postgresql'=> true, 'postgresql-console'=> true, 'postscr'=> true, 'postscript'=> true, 'pot'=> true, 'pov'=> true, 'powershell'=> true, 'praat'=> true, 'progress'=> true, 'prolog'=> true, 'promql'=> true, 'properties'=> true, 'proto'=> true, 'protobuf'=> true, 'ps1'=> true, 'ps1con'=> true, 'psm1'=> true, 'psql'=> true, 'psysh'=> true, 'pug'=> true, 'puppet'=> true, 'py'=> true, 'py2'=> true, 'py2tb'=> true, 'py3'=> true, 'py3tb'=> true, 'pycon'=> true, 'pypy'=> true, 'pypylog'=> true, 'pyrex'=> true, 'pytb'=> true, 'python'=> true, 'python2'=> true, 'python3'=> true, 'pyx'=> true, 'qbasic'=> true, 'qbs'=> true, 'qml'=> true, 'qvt'=> true, 'qvto'=> true, 'r'=> true, 'racket'=> true, 'ragel'=> true, 'ragel-c'=> true, 'ragel-cpp'=> true, 'ragel-d'=> true, 'ragel-em'=> true, 'ragel-java'=> true, 'ragel-objc'=> true, 'ragel-rb'=> true, 'ragel-ruby'=> true, 'raku'=> true, 'raw'=> true, 'rb'=> true, 'rbcon'=> true, 'rconsole'=> true, 'rd'=> true, 'reason'=> true, 'reasonml'=> true, 'rebol'=> true, 'red'=> true, 'red/system'=> true, 'redcode'=> true, 'registry'=> true, 'resource'=> true, 'resourcebundle'=> true, 'rest'=> true, 'restructuredtext'=> true, 'rexx'=> true, 'rhtml'=> true, 'ride'=> true, 'rkt'=> true, 'rnc'=> true, 'rng-compact'=> true, 'roboconf-graph'=> true, 'roboconf-instances'=> true, 'robotframework'=> true, 'rout'=> true, 'rql'=> true, 'rs'=> true, 'rsl'=> true, 'rst'=> true, 'rts'=> true, 'ruby'=> true, 'rust'=> true, 's'=> true, 'sage'=> true, 'salt'=> true, 'sarl'=> true, 'sas'=> true, 'sass'=> true, 'sbatch'=> true, 'sc'=> true, 'scala'=> true, 'scaml'=> true, 'scd'=> true, 'scdoc'=> true, 'scheme'=> true, 'scilab'=> true, 'scm'=> true, 'scss'=> true, 'sgf'=> true, 'sh'=> true, 'shell'=> true, 'shell-session'=> true, 'shen'=> true, 'shex'=> true, 'shexc'=> true, 'sieve'=> true, 'silver'=> true, 'singularity'=> true, 'slash'=> true, 'slim'=> true, 'sls'=> true, 'slurm'=> true, 'smali'=> true, 'smalltalk'=> true, 'smarty'=> true, 'sml'=> true, 'snobol'=> true, 'snowball'=> true, 'solidity'=> true, 'sources.list'=> true, 'sourceslist'=> true, 'sp'=> true, 'sparql'=> true, 'spec'=> true, 'spitfire'=> true, 'splus'=> true, 'sql'=> true, 'sqlite3'=> true, 'squeak'=> true, 'squid'=> true, 'squid.conf'=> true, 'squidconf'=> true, 'ssp'=> true, 'st'=> true, 'stan'=> true, 'stata'=> true, 'supercollider'=> true, 'sv'=> true, 'swift'=> true, 'swig'=> true, 'systemverilog'=> true, 't-sql'=> true, 'tads3'=> true, 'tap'=> true, 'tasm'=> true, 'tcl'=> true, 'tcsh'=> true, 'tcshcon'=> true, 'tea'=> true, 'teraterm'=> true, 'teratermmacro'=> true, 'termcap'=> true, 'terminfo'=> true, 'terraform'=> true, 'tex'=> true, 'text'=> true, 'tf'=> true, 'thrift'=> true, 'tid'=> true, 'tnt'=> true, 'todotxt'=> true, 'toml'=> true, 'trac-wiki'=> true, 'trafficscript'=> true, 'treetop'=> true, 'ts'=> true, 'tsql'=> true, 'ttl'=> true, 'turtle'=> true, 'twig'=> true, 'typescript'=> true, 'typoscript'=> true, 'typoscriptcssdata'=> true, 'typoscripthtmldata'=> true, 'ucode'=> true, 'udiff'=> true, 'unicon'=> true, 'urbiscript'=> true, 'usd'=> true, 'usda'=> true, 'v'=> true, 'vala'=> true, 'vapi'=> true, 'vb.net'=> true, 'vbnet'=> true, 'vbscript'=> true, 'vcl'=> true, 'vclsnippet'=> true, 'vclsnippets'=> true, 'vctreestatus'=> true, 'velocity'=> true, 'verilog'=> true, 'vfp'=> true, 'vgl'=> true, 'vhdl'=> true, 'vim'=> true, 'wdiff'=> true, 'webidl'=> true, 'whiley'=> true, 'winbatch'=> true, 'winbugs'=> true, 'x10'=> true, 'xbase'=> true, 'xml'=> true, 'xml+cheetah'=> true, 'xml+django'=> true, 'xml+erb'=> true, 'xml+evoque'=> true, 'xml+genshi'=> true, 'xml+jinja'=> true, 'xml+kid'=> true, 'xml+lasso'=> true, 'xml+mako'=> true, 'xml+myghty'=> true, 'xml+php'=> true, 'xml+ruby'=> true, 'xml+smarty'=> true, 'xml+spitfire'=> true, 'xml+velocity'=> true, 'xorg.conf'=> true, 'xq'=> true, 'xql'=> true, 'xqm'=> true, 'xquery'=> true, 'xqy'=> true, 'xslt'=> true, 'xten'=> true, 'xtend'=> true, 'xul+mozpreproc'=> true, 'yaml'=> true, 'yaml+jinja'=> true, 'yang'=> true, 'zeek'=> true, 'zephir'=> true, 'zig'=> true, 'zsh'=> true,]
Convenience class for weighted consistent hash rings.
Definition HashRing.php:44
getLiveLocations( $item, $limit)
Get the location of an item on the "live" ring, as well as the next locations.
Definition HashRing.php:257
int[] $ejectExpiryByLocation
Map of (location => UNIX timestamp)
Definition HashRing.php:50
const KEY_LOCATION
Definition HashRing.php:63
getLiveLocationWeights()
Get the map of "live" locations to weight (does not include zero weight items)
Definition HashRing.php:267
array[] $baseRing
Non-empty position-ordered list of (position, location name)
Definition HashRing.php:53
init(array $map, $algo, array $ejections)
Definition HashRing.php:87
getLiveLocation( $item)
Get the location of an item on the "live" ring.
Definition HashRing.php:245
int[] $weightByLocation
Non-empty (location => integer weight)
Definition HashRing.php:48
getCurrentTime()
Definition HashRing.php:436
unserialize( $serialized)
Definition HashRing.php:452
getLocation( $item)
Get the location of an item on the ring.
Definition HashRing.php:112
findNodeIndexForPosition( $position, $ring)
Definition HashRing.php:170
array[] $liveRing
Non-empty position-ordered list of (position, location name)
Definition HashRing.php:55
getNodePositionQuartet( $nodeGroupName)
Definition HashRing.php:359
__unserialize( $data)
Definition HashRing.php:456
getNextClockwiseNodeIndex( $i, $ring)
Definition HashRing.php:378
getItemPosition( $item)
Definition HashRing.php:337
buildLocationRing(array $weightByLocation)
Definition HashRing.php:285
const KEY_POS
Definition HashRing.php:62
__serialize()
Definition HashRing.php:444
getLocationWeights()
Get the map of locations to weight (does not include zero weight items)
Definition HashRing.php:213
getLocations( $item, $limit, $from=self::RING_ALL)
Get the location of an item on the ring followed by the next ring locations.
Definition HashRing.php:126
getLiveRing()
Get the "live" hash ring (which does not include ejected locations)
Definition HashRing.php:394
string $algo
Hashing algorithm for hash()
Definition HashRing.php:46
__construct(array $map, $algo='sha1', array $ejections=[])
Make a consistent hash ring given a set of locations and their weight values.
Definition HashRing.php:78
ejectFromLiveRing( $location, $ttl)
Remove a location from the "live" hash ring.
Definition HashRing.php:225
foreach( $res as $row) $serialized