Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get/getMulti return RES_NOTFOUND after buffered increment with result RES_UNKNOWN_READ_FAILURE with OPT_BINARY_PROTOCOL #554

Open
degtyaryov opened this issue Jan 25, 2024 · 3 comments

Comments

@degtyaryov
Copy link

degtyaryov commented Jan 25, 2024

Hello!

Code:

<?php

$key = 'KEY';
$value = 'VALUE';

$memcached = new Memcached();

$memcached->addServer('127.0.0.1', '11211', 1);

$memcached->setOption( Memcached::OPT_BINARY_PROTOCOL, true);

$memcached->setOption(Memcached::OPT_BUFFER_WRITES, true);
$memcached->setOption(Memcached::OPT_NOREPLY, true);

$memcached->set($key, $value);
echo 'increment ', $key, ' ', $memcached->increment($key, 1, 1, 600), ' ', $memcached->getResultMessage(), "\n";
echo 'get ', $key, ' ', $memcached->get($key), ' ', $memcached->getResultMessage(), "\n";

Expect:

increment KEY  SUCCESS
get KEY VALUE SUCCESS

Result:

increment KEY  SUCCESS
get KEY  NOT FOUND

libmemcached send request 1 TCP package for set, incr and get

server memcached return 1 TCP package with 2 results:

  • for incr return "Non-numeric server-side value for incr or decr"
  • for get return "VALUE"

But php-memcached for get return NOT FOUND

Text protocol:

<?php

$key = 'KEY';
$value = 'VALUE';

$memcached = new Memcached();

$memcached->addServer('127.0.0.1', '11211', 1);


$memcached->setOption(Memcached::OPT_BUFFER_WRITES, true);
$memcached->setOption(Memcached::OPT_NOREPLY, true);

$memcached->set($key, $value);
echo 'increment ', $key, ' ', $memcached->increment($key, 1), ' ', $memcached->getResultMessage(), "\n";
echo 'get ', $key, ' ', $memcached->get($key), ' ', $memcached->getResultMessage(), "\n";

Expect:

increment KEY  SUCCESS
get KEY VALUE SUCCESS

Result:

increment KEY  SUCCESS
get KEY VALUE SUCCESS

libmemcached send request 1 TCP package for incr and get

server memcached return 1 TCP package with 1 results:

  • for get return "VALUE"
@sodabrew
Copy link
Contributor

Thank you for the minimal repro!

This is most likely a library issue rather than the php plugin code.

What version of libmemcache are you using? Try libmemcache-awesome for more recent updates and bug fixes.

@degtyaryov
Copy link
Author

degtyaryov commented Jan 26, 2024

I tested this example on:

  • Fedora 37
  • PHP 8.2.11
  • php-memcached 3.2.0
  • libmemcached-awesome 1.1.3
  • memcached-1.4.39 on Fedora 25

I checked it in C language

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libmemcached/memcached.h>

int main(int argc, char *argv[])
{
    char *key= "KEY";
    char *value= "VALUE";

    memcached_server_st *servers = NULL;
    memcached_st *memc;
    memcached_return rc;
    
    uint64_t incr = UINT64_MAX;
    size_t key_length = strlen(key);

    char return_key[MEMCACHED_MAX_KEY];
    size_t return_key_length;
    char *return_value;
    size_t return_value_length;
    uint32_t flags;
    
    
    const char* keys[] = {key};
    size_t keys_length[] = {key_length};
    
    memcached_server_st *memcached_servers_parse (const char *server_strings);

    memc= memcached_create(NULL);
    
    servers= memcached_server_list_append(servers, "127.0.0.1", 11211, &rc);
    rc= memcached_server_push(memc, servers);
    
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1);
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 1);

   
    rc= memcached_set(memc, key, key_length, value, strlen(value), (time_t)0, (uint32_t)0);
    rc = memcached_increment_with_initial(memc, key, key_length, 1, 1, (time_t)600, &incr);

    rc = memcached_mget(memc, keys, keys_length, 1);
    
    return_value = memcached_fetch(memc, return_key, &return_key_length, &return_value_length, &flags, &rc);
    fprintf(stdout,"Value: %s\n",return_value);
    
    return_value = memcached_fetch(memc, return_key, &return_key_length, &return_value_length, &flags, &rc);
    fprintf(stdout,"Value: %s\n",return_value);

    memcached_quit(memc);
    
    return 0;
}

Expect:

Value: VALUE

Result:

Value: (null)

libmemcached send request 1 TCP package for set, incr and get

server memcached return 1 TCP package with 2 results:

  • for incr return "Non-numeric server-side value for incr or decr"
  • for get return "VALUE"

I checked it in C language on Fedora 39 with libmemcached-awesome 1.1.4 memcached-1.6.21
Result:

Value: (null)

libmemcached send request 2 TCP package:

  • for set and incr
  • for get

server memcached return 1 TCP package with 2 results:

  • for incr return "Non-numeric server-side value for incr or decr"
  • for get return "VALUE"

@degtyaryov
Copy link
Author

Another example:

<?php

$key1 = 'KEY1';
$key2 = 'KEY2';

$value1 = 'VALUE1';
$value2 = 'VALUE2';

$memcached = new Memcached();

$memcached->addServer('127.0.0.1', '11212', 1);

$memcached->setOption( Memcached::OPT_BINARY_PROTOCOL, true);

$memcached->set($key1, $value1);

$memcached->setOption(Memcached::OPT_BUFFER_WRITES, true);
$memcached->setOption(Memcached::OPT_NOREPLY, true);

for ($i = 0; $i < 100; ++$i) {
        $memcached->delete($key2);
}
echo 'get ', $key1, ' ', $memcached->get($key1), ' ', $memcached->getResultMessage(), "\n";

Expect:

get KEY1 VALUE1 SUCCESS

Result:

get KEY1 VALUE1 SUCCESS

Next execute result random:

get KEY1  NOT FOUND

C Language:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libmemcached/memcached.h>

int main(int argc, char *argv[])
{
    char *key1= "KEY1";
    char *value1= "VALUE1";
    
    char *key2= "KEY2";
    char *value2= "VALUE2";

    memcached_server_st *servers = NULL;
    memcached_st *memc;
    memcached_return rc;
    
    size_t key1_length = strlen(key1);
    size_t key2_length = strlen(key2);

    char *return_value;
    size_t return_value_length;
    uint32_t flags;
    
    memcached_server_st *memcached_servers_parse (const char *server_strings);

    memc= memcached_create(NULL);
    
    servers= memcached_server_list_append(servers, "127.0.0.1", 11211, &rc);
    rc= memcached_server_push(memc, servers);
    
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
    
    rc= memcached_set(memc, key1, key1_length, value1, strlen(value1), (time_t)0, (uint32_t)0);
    
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1);
    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 1);

    for (int i = 0; i < 100; ++i) {
        rc= memcached_delete(memc, key2, key2_length, (time_t)0);
    }

    return_value = memcached_get(memc, key1, key1_length, &return_value_length, &flags, &rc);
    fprintf(stdout,"Value: %s\n",return_value);

Expect:

Value: VALUE1

Result:

Value: (null)

Memcached to delete a non-existent key sometimes returns a response.

Memcache Protocol, Delete Quietly Response
    Magic: Response (129)
    Opcode: Delete Quietly (20)
    Key Length: 0
    Extras length: 0
    Data type: Raw bytes (0)
    Status: Key not found (1)
        [Expert Info (Note/Response): Delete Quietly: Key not found]
            [Delete Quietly: Key not found]
            [Severity level: Note]
            [Group: Response]
    [Value length: 9]
    Total body length: 9
    Opaque: 1835008
    CAS: 0
    Value: Not found

Sometimes there is no answer.

Questions:

  1. Why does the memcached server send responses and generate unnecessary network traffic? After all, the absence of a key is not an error. Why out of 100 requests there are only 30 answers?
  2. Why does the libmemcached discard everything received after such a response?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants