続・マルチスレッドなmemcachedのincrが想定通り動かない時がある件

マルチスレッドなmemcachedのincrが想定通り動かない時がある件についてです。
A:memcached 1.4.4に以下のパッチを適用

*** memcached.c_org     2009-11-27 14:45:13.000000000 +0900
--- memcached.c 2010-03-30 21:03:08.000000000 +0900
***************
*** 54,59 ****
--- 54,60 ----
  #endif
  #endif

+ pthread_mutex_t test_lock = PTHREAD_MUTEX_INITIALIZER;
  /*
   * forward declarations
   */
***************
*** 1017,1022 ****
--- 1018,1024 ----
                  req->message.body.expiration);
      }

+ pthread_mutex_lock(&test_lock);
      it = item_get(key, nkey);
      if (it && (c->binary_header.request.cas == 0 ||
                 c->binary_header.request.cas == ITEM_get_cas(it))) {
***************
*** 1082,1087 ****
--- 1084,1090 ----

          write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
      }
+ pthread_mutex_unlock(&test_lock);
  }

  static void complete_update_bin(conn *c) {

B:frsyukiさんの検証プログラムに以下のパッチを適用

*** test.c_org  2010-03-30 21:08:36.000000000 +0900
--- test.c      2010-03-30 21:08:56.000000000 +0900
***************
*** 8,14 ****

  const char* g_host = "127.0.0.1";
  unsigned short g_port = 11211;
! bool g_binary = false;

  const char* g_key = "test";
  size_t g_key_len = 4;
--- 8,14 ----

  const char* g_host = "127.0.0.1";
  unsigned short g_port = 11211;
! bool g_binary = true;

  const char* g_key = "test";
  size_t g_key_len = 4;

AなしBなし

$ while true; do ./test_ascii; done
expected: 100000
result: 100000
expected: 100000
result: 99960
expected: 100000
result: 100000
expected: 100000
result: 99998

AありBなし

$ while true; do ./test_ascii; done
expected: 100000
result: 99976
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 99997

AなしBあり

$ while true; do ./test_binary; done
expected: 100000
result: 99998
expected: 100000
result: 100000
expected: 100000
result: 99985
expected: 100000
result: 100000

AありBあり

$ while true; do ./test_binary; done
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000
expected: 100000
result: 100000

いかがでしょう?

2010/03/31追記

ASCIIプロトコルについても修正しました。手元の環境ではAありBなしでも正しく動いています。

*** memcached.c_org     2009-11-27 14:45:13.000000000 +0900
--- memcached.c 2010-03-31 01:29:22.000000000 +0900
***************
*** 54,59 ****
--- 54,60 ----
  #endif
  #endif

+ pthread_mutex_t incr_lock = PTHREAD_MUTEX_INITIALIZER;
  /*
   * forward declarations
   */
***************
*** 1017,1022 ****
--- 1018,1024 ----
                  req->message.body.expiration);
      }

+     pthread_mutex_lock(&incr_lock);
      it = item_get(key, nkey);
      if (it && (c->binary_header.request.cas == 0 ||
                 c->binary_header.request.cas == ITEM_get_cas(it))) {
***************
*** 1082,1087 ****
--- 1084,1090 ----

          write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
      }
+     pthread_mutex_unlock(&incr_lock);
  }

  static void complete_update_bin(conn *c) {
***************
*** 2780,2785 ****
--- 2783,2789 ----
          return;
      }

+     pthread_mutex_lock(&incr_lock);
      it = item_get(key, nkey);
      if (!it) {
          pthread_mutex_lock(&c->thread->stats.mutex);
***************
*** 2791,2796 ****
--- 2795,2801 ----
          pthread_mutex_unlock(&c->thread->stats.mutex);

          out_string(c, "NOT_FOUND");
+         pthread_mutex_unlock(&incr_lock);
          return;
      }

***************
*** 2806,2811 ****
--- 2811,2817 ----
          break;
      }
      item_remove(it);         /* release our reference */
+     pthread_mutex_unlock(&incr_lock);
  }

  /*

ここまでの内容をまとめてコミュニティに報告しました。
Issue 127 - memcached - incr/decr operations are not thread safe. - Project Hosting on Google Code