Skip to content

libmemcached 1.0.16 is totally incompatible with session.lazy_write #310

Closed
@aeijdenberg

Description

@aeijdenberg

If session.lazy-write is enabled (which it is by default in PHP 7) calls to session-write-close() will check if the session is modified, and if not will call PS_UPDATE_TIMESTAMP_FUNC in php-memcached which in turn calls memcached_touch().

In libmemcache 1.0.15, this was broken because when reading the response, it ignored the 4 bytes returned in the body by the server, which caused the next request to fail. See bug 1104642.

This was attempted to be fixed with the following in 1.0.16. This is also broken, because it ignores the bodylen and instead tries to read 32 bytes (bodylen is normally 4 in this context):

-        WATCHPOINT_ASSERT(bodylen == 0);
+        if (bodylen != 0)
+        {
+          char touch_buffer[32]; // @todo document this number
+          rc= memcached_safe_read(instance, buffer, sizeof(touch_buffer));
+        }
         return MEMCACHED_SUCCESS;

What this means is that in 1.0.16, calls to TOUCH succeed, but if successful, they always take 5 seconds, while it times out waiting for the remaining 28 bytes of data. It also swallows the error, so that the caller thinks it succeed, just very slowly.

To trigger this, use PHP 7 and libmemcached 1.0.16:

<?php

ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', "memcache:11211");
ini_set('memcached.sess_prefix', "foo");
ini_set('memcached.sess_locking', '1');

session_id("foo");

$start1 = microtime(true);
session_start();
$_SESSION["a"] = "b";
session_write_close();
$end1 = microtime(true);

$start2 = microtime(true);
session_start();
session_write_close();
$end2 = microtime(true);

echo "Time to open / close session:".round($end1 - $start1, 3)."\n";
echo "Time to open / close session:".round($end2 - $start2, 3)."\n";

The second time will always be just over 5 seconds - this bug only fires if the key already exists.

Looking further, it seems like 1.0.17 attempts to fix the breakage caused in 1.0.16:

+        rc= MEMCACHED_SUCCESS;
+        if (bodylen == 4) // The four byte read is a bug?
+        {
+          char touch_buffer[4]; // @todo document this number
+          rc= memcached_safe_read(instance, touch_buffer, sizeof(touch_buffer));
+        }
+        return memcached_set_error(*instance, rc, MEMCACHED_AT);

While this does seem to work, note that if the server ever does decide to return a number of bytes not equal to 4 or 0, then this will regress back to the same error condition as 1.0.15, where success is returned, and the bytes will be incorrectly read as part of the next response.

Looks like this bug reared it's head here too a while back, the result being that 1.0.14 was dropped.

I mention all this as I believe 1.0.16 is the default on CentOS7 and I note the README in this repo says that 1.0.16 or higher is recommended.

Not sure what best course of action is... but I feel better for reporting it. Maybe just an update to the README and a regression test based on the above? Maybe fail fast if lazy sessions are detected to be enabled with an incompatible version of libmemcached?

Note I think the bug can be avoided by using ASCII mode for TOUCH.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions