42 Memcached::OPT_NO_BLOCK =>
false,
43 Memcached::OPT_BUFFER_WRITES =>
false
47 Memcached::OPT_NO_BLOCK =>
true,
48 Memcached::OPT_BUFFER_WRITES =>
true
68 parent::__construct( $params );
72 'compress_threshold' => 1500,
73 'connect_timeout' => 0.5,
75 'serializer' =>
'php',
76 'use_binary_protocol' =>
false,
77 'allow_tcp_nagle_delay' =>
true
80 if ( $params[
'persistent'] ) {
84 $connectionPoolId = md5(
serialize( $params ) );
85 $syncClient =
new Memcached(
"$connectionPoolId-sync" );
87 $asyncClient =
new Memcached(
"$connectionPoolId-async" );
104 ini_set(
'memcached.compression_threshold', $params[
'compress_threshold'] );
117 if ( $client->getServerList() ) {
118 $this->logger->debug( __METHOD__ .
": pre-initialized client instance." );
123 $this->logger->debug( __METHOD__ .
": initializing new client instance." );
126 Memcached::OPT_NO_BLOCK =>
false,
127 Memcached::OPT_BUFFER_WRITES =>
false,
129 Memcached::OPT_BINARY_PROTOCOL => $params[
'use_binary_protocol'],
131 Memcached::OPT_CONNECT_TIMEOUT => $params[
'connect_timeout'] * 1000,
132 Memcached::OPT_SEND_TIMEOUT => $params[
'timeout'],
133 Memcached::OPT_RECV_TIMEOUT => $params[
'timeout'],
134 Memcached::OPT_POLL_TIMEOUT => $params[
'timeout'] / 1000,
136 Memcached::OPT_TCP_NODELAY => !$params[
'allow_tcp_nagle_delay'],
138 Memcached::OPT_LIBKETAMA_COMPATIBLE =>
true
140 if ( isset( $params[
'retry_timeout'] ) ) {
141 $options[Memcached::OPT_RETRY_TIMEOUT] = $params[
'retry_timeout'];
143 if ( isset( $params[
'server_failure_limit'] ) ) {
144 $options[Memcached::OPT_SERVER_FAILURE_LIMIT] = $params[
'server_failure_limit'];
146 if ( $params[
'serializer'] ===
'php' ) {
147 $options[Memcached::OPT_SERIALIZER] = Memcached::SERIALIZER_PHP;
148 } elseif ( $params[
'serializer'] ===
'igbinary' ) {
150 if ( !Memcached::HAVE_IGBINARY ) {
151 throw new RuntimeException(
152 __CLASS__ .
': the igbinary extension is not available ' .
153 'but igbinary serialization was requested.'
156 $options[Memcached::OPT_SERIALIZER] = Memcached::SERIALIZER_IGBINARY;
159 if ( !$client->setOptions( $options ) ) {
160 throw new RuntimeException(
161 "Invalid options: " . json_encode( $options, JSON_PRETTY_PRINT )
166 foreach ( $params[
'servers'] as $host ) {
167 if ( preg_match(
'/^\[(.+)\]:(\d+)$/', $host, $m ) ) {
168 $servers[] = [ $m[1], (int)$m[2] ];
169 } elseif ( preg_match(
'/^([^:]+):(\d+)$/', $host, $m ) ) {
170 $servers[] = [ $m[1], (int)$m[2] ];
172 $servers[] = [ $host, false ];
176 if ( !$client->addServers( $servers ) ) {
177 throw new RuntimeException(
"Failed to inject server address list" );
181 protected function doGet( $key, $flags = 0, &$casToken =
null ) {
182 $getToken = ( $casToken === self::PASS_BY_REF );
185 $this->
debug(
"get($key)" );
193 $flags = Memcached::GET_EXTENDED;
194 $res = $client->get( $routeKey,
null, $flags );
195 if ( is_array(
$res ) ) {
196 $result =
$res[
'value'];
197 $casToken =
$res[
'cas'];
202 $result = $client->get( $routeKey );
208 protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
209 $this->
debug(
"set($key)" );
213 $result = $client->set( $routeKey, $value, $this->
fixExpiry( $exptime ) );
215 return ( $result ===
false && $client->getResultCode() === Memcached::RES_NOTSTORED )
221 protected function doCas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
222 $this->
debug(
"cas($key)" );
235 $this->
debug(
"delete($key)" );
239 $result = $client->delete( $routeKey );
241 return ( $result ===
false && $client->getResultCode() === Memcached::RES_NOTFOUND )
247 protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
248 $this->
debug(
"add($key)" );
260 public function incr( $key, $value = 1, $flags = 0 ) {
261 $this->
debug(
"incr($key)" );
269 public function decr( $key, $value = 1, $flags = 0 ) {
270 $this->
debug(
"decr($key)" );
279 $this->
debug(
"incrWithInit($key)" );
285 $result = $client->increment( $routeKey, $step );
287 if ( $newValue ===
false && !$this->
getLastError( $watchPoint ) ) {
289 $result = $client->add( $routeKey, $init, $this->
fixExpiry( $exptime ) );
290 $newValue = $this->
checkResult( $key, $result ) ? $init :
false;
291 if ( $newValue ===
false && !$this->
getLastError( $watchPoint ) ) {
293 $result = $client->increment( $routeKey, $step );
304 foreach ( $valueByKey as $value ) {
323 static $statusByCode = [
337 if ( $result !==
false ) {
342 $code = $client->getResultCode();
344 case Memcached::RES_SUCCESS:
346 case Memcached::RES_DATA_EXISTS:
347 case Memcached::RES_NOTSTORED:
348 case Memcached::RES_NOTFOUND:
349 $this->
debug(
"result: " . $client->getResultMessage() );
352 $msg = $client->getResultMessage();
354 if ( $key !==
false ) {
355 $server = $client->getServerByKey( $key );
356 $logCtx[
'memcached-server'] =
"{$server['host']}:{$server['port']}";
357 $logCtx[
'memcached-key'] = $key;
358 $msg =
"Memcached error for key \"{memcached-key}\" " .
359 "on server \"{memcached-server}\": $msg";
361 $msg =
"Memcached error: $msg";
363 $this->logger->error( $msg, $logCtx );
364 $this->
setLastError( $statusByCode[$code] ?? self::ERR_UNEXPECTED );
370 $this->
debug(
'getMulti(' . implode(
', ',
$keys ) .
')' );
373 foreach (
$keys as $key ) {
382 if ( is_array( $resByRouteKey ) ) {
384 foreach ( $resByRouteKey as $routeKey => $value ) {
394 protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
395 $this->
debug(
'setMulti(' . implode(
', ', array_keys( $data ) ) .
')' );
398 $dataByRouteKey = [];
399 foreach ( $data as $key => $value ) {
405 if ( $this->
fieldHasFlags( $flags, self::WRITE_BACKGROUND ) ) {
409 $result = @$client->setMulti( $dataByRouteKey, $exptime );
415 $result = @$client->setMulti( $dataByRouteKey, $exptime );
422 $this->
debug(
'deleteMulti(' . implode(
', ',
$keys ) .
')' );
425 foreach (
$keys as $key ) {
431 if ( $this->
fieldHasFlags( $flags, self::WRITE_BACKGROUND ) ) {
433 $resultArray = $client->deleteMulti( $routeKeys ) ?: [];
440 foreach ( $resultArray as $code ) {
441 if ( !in_array( $code, [
true, Memcached::RES_NOTFOUND ],
true ) ) {
451 $this->
debug(
"touch($key)" );
460 if ( is_int( $value ) ) {
464 $serializer = $this->syncClient->getOption( Memcached::OPT_SERIALIZER );
465 if ( $serializer === Memcached::SERIALIZER_PHP ) {
467 } elseif ( $serializer === Memcached::SERIALIZER_IGBINARY ) {
468 return igbinary_serialize( $value );
471 throw new UnexpectedValueException( __METHOD__ .
": got serializer '$serializer'." );
479 $serializer = $this->syncClient->getOption( Memcached::OPT_SERIALIZER );
480 if ( $serializer === Memcached::SERIALIZER_PHP ) {
482 } elseif ( $serializer === Memcached::SERIALIZER_IGBINARY ) {
483 return igbinary_unserialize( $value );
486 throw new UnexpectedValueException( __METHOD__ .
": got serializer '$serializer'." );
493 if ( $this->syncClientIsBuffering ) {
494 throw new RuntimeException(
"The main (unbuffered I/O) client is locked" );
497 if ( $this->hasUnflushedChanges ) {
499 $this->syncClient->fetch();
500 if ( $this->asyncClient ) {
501 $this->asyncClient->fetch();
503 $this->hasUnflushedChanges =
false;
513 if ( $this->asyncClient ) {
518 $this->syncClientIsBuffering =
true;
519 $this->syncClient->setOptions( self::$OPTS_ASYNC_WRITES );
528 $this->hasUnflushedChanges =
true;
530 if ( !$this->asyncClient ) {
532 $client->setOptions( self::$OPTS_SYNC_WRITES );
533 $this->syncClientIsBuffering =
false;
getLastError( $watchPoint=0)
Get the "last error" registry.
setLastError( $error)
Set the "last error" registry due to a problem encountered during an attempted operation.
watchErrors()
Get a "watch point" token that can be used to get the "last error" to occur after now.
fieldHasFlags( $field, $flags)
guessSerialValueSize( $value, $depth=0, &$loops=0)
Estimate the size of a variable once serialized.
isInteger( $value)
Check if a value is an integer.
Base class for memcached clients.
validateKeyAndPrependRoute( $key)
A wrapper class for the PECL memcached client.
doAdd( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
bool $syncClientIsBuffering
Whether the non-buffering client is locked from use.
doGet( $key, $flags=0, &$casToken=null)
doGetMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
doCas( $casToken, $key, $value, $exptime=0, $flags=0)
Check and set an item.
doSet( $key, $value, $exptime=0, $flags=0)
Set an item.
incr( $key, $value=1, $flags=0)
Increase stored value of $key by $value while preserving its TTL.
doChangeTTL( $key, $exptime, $flags)
doDeleteMulti(array $keys, $flags=0)
decr( $key, $value=1, $flags=0)
Decrease stored value of $key by $value while preserving its TTL.
releaseAsyncClient( $client)
setNewPreparedValues(array $valueByKey)
Make a "generic" reversible cache key from the given components.
initializeClient(Memcached $client, array $params, array $options)
Initialize the client only if needed and reuse it otherwise.
doIncrWithInit( $key, $exptime, $step, $init, $flags)
bool $hasUnflushedChanges
Whether the non-buffering client should be flushed before use.
static array $OPTS_SYNC_WRITES
Memcached options.
doSetMulti(array $data, $exptime=0, $flags=0)
static array $OPTS_ASYNC_WRITES
Memcached options.
checkResult( $key, $result)
Check the return value from a client method call and take any necessary action.
doDelete( $key, $flags=0)
Delete an item.
__construct( $params)
Available parameters are:
Memcached null $asyncClient