libmemcache のパッチを作ってみる

はい。というわけで前回の「libmemcache を使ってみる」の続きです。

libmemcache 使ったクライアントでアクセスしながら、サーバ側で memcached 落とすと利用可能な次のサーバに行って欲しいけど、実際やってみたら

CODE:
  1. [ERROR@1186044634.069779] mcm_buf_read():361: read(2) failed: Operation now in progress: server unexpectedly closed connection

っつわれて exit するやんけ、という話なので、パッチを作ってみました。

まずは延々と set し続けるテストプログラム、mctest.cc を作りました。

C++:
  1. #include <iostream>
  2. #include <string>
  3. #include <map>
  4.  
  5. #include <memcache.h>
  6. #include <boost/lexical_cast.hpp>
  7.  
  8. using namespace std;
  9.  
  10. class memcacheWrapper {
  11. public:
  12.     memcacheWrapper() {
  13.         _mc = mc_new();
  14.     }
  15.  
  16.     ~memcacheWrapper() {
  17.         mc_free(_mc);
  18.     }
  19.  
  20.     struct memcache* _mc;
  21. };
  22.  
  23. class memcacheReqWrapper {
  24. public:
  25.     memcacheReqWrapper() {
  26.         _mcr = mc_req_new();
  27.     }
  28.  
  29.     ~memcacheReqWrapper() {
  30.         mc_req_free(_mcr);
  31.     }
  32.  
  33.     struct memcache_req* _mcr;
  34. };
  35.  
  36. int main(int argc, char* argv[]) {
  37.     // 初期化
  38.     memcacheWrapper mr;
  39.     int ret = mc_server_add4(mr._mc, "127.0.0.1:11211");
  40.     if (ret != MCM_ERR_NONE) {
  41.         cerr <<"mc_server_add4 failed" <<endl;
  42.         exit(1);
  43.     }
  44.     ret = mc_server_add4(mr._mc, "127.0.0.1:11212");
  45.     if (ret != MCM_ERR_NONE) {
  46.         cerr <<"mc_server_add4 failed" <<endl;
  47.         exit(1);
  48.     }
  49.     ret = mc_server_add4(mr._mc, "127.0.0.1:11213");
  50.     if (ret != MCM_ERR_NONE) {
  51.         cerr <<"mc_server_add4 failed" <<endl;
  52.         exit(1);
  53.     }
  54.  
  55.     // 適当に値追加
  56.     for (int i = 0; i <1000000; i++) {
  57.         string k = "k" + boost::lexical_cast<string>(i);
  58.         string v = "v" + boost::lexical_cast<string>(i);
  59.         cerr <<k <<" : " <<v <<endl;
  60.         ret = mc_set(mr._mc,
  61.                      const_cast<char*>(k.c_str()),
  62.                      k.length(),
  63.                      const_cast<char*>(v.c_str()),
  64.                      v.length(),
  65.                      6000,
  66.                      MCM_RES_FREE_ON_DELETE);
  67.         if (ret != MCM_ERR_NONE) {
  68.             cerr <<"mc_set failed: " <<k <<" : " <<v <<endl;
  69.             exit(1);
  70.         }
  71.     }
  72.  
  73.     return 0;
  74. }

で、以下でコンパイル。

CODE:
  1. [maihara@debian:~/work] $ g++ -lmemcache -o mctest mctest.cc

localhost ですがサーバ側として memcached を 3 つ起動。

CODE:
  1. [maihara@debian:~/work] $ sudo /usr/bin/memcached -m 64 -p 11211 -u root &
  2. [maihara@debian:~/work] $ sudo /usr/bin/memcached -m 64 -p 11212 -u root &
  3. [maihara@debian:~/work] $ sudo /usr/bin/memcached -m 64 -p 11213 -u root &

で、先ほどの mctest を ./mctest で動かしつつ、memcached を 1 つ kill -9 すると頭の方にあるエラーを吐いて mctest 終了。イヤン。こりゃどうも libmemcache の実装に問題がありそうだというわけでソースをダウンロード。

CODE:
  1. [maihara@debian:~/work] $ wget http://people.freebsd.org/~seanc/libmemcache/libmemcache-1.4.0.rc2.tar.bz2
  2. [maihara@debian:~/work] $ tar xjvf libmemcache-1.4.0.rc2.tar.bz2
  3. [maihara@debian:~/work] $ libmemcache-1.4.0.rc2
  4. [maihara@debian:~/work/libmemcache-1.4.0.rc2] $ g 'server unexpectedly closed connection' **/*.c
  5. src/buffer.c:    MCM_ERR_MSG(MCM_ERR_SYS_READ, "server unexpectedly closed connection");

で src/buffer.c を見てみると、mcm_buf_read 関数の中で read が 0 を返すと終了しちゃってる模様。。なんですが、read が 0 を返してもエラーじゃないので(エラーは -1)、そのときは処理を継続するような変更と、deactivate な状態になって利用可能じゃなくなったサーバが復旧してきたのを検知するコードを、src/memcache.c のサーバを探す mcm_server_find_func 関数に入れてみたのが以下のパッチ。

CODE:
  1. --- memcache.c.bak      2007-08-03 12:36:22.000000000 +0900
  2. +++ memcache.c  2007-08-03 12:27:50.000000000 +0900
  3. @@ -956,6 +956,7 @@
  4.    /* There are a few error codes that require special cases for */
  5.    switch (errcode) {
  6.    case MCM_ERR_MC_SERV_LIST:
  7. +  case MCM_ERR_SYS_READ:
  8.      if (ectxt->cont == 'n')
  9.        ectxt->cont = 'y';
  10.      break;
  11. @@ -1558,7 +1559,8 @@
  12.      goto resend;
  13.    }
  14.  
  15. -  if (bytes_read == 0) {
  16. +  if (bytes_read <= 0) {
  17. +    if (bytes_read == 0) goto resend;
  18.      switch (errno) {
  19.      case EAGAIN:
  20.      case EINTR:
  21. @@ -2432,6 +2434,10 @@
  22.        else
  23.         idx++;
  24.  
  25. +      mcm_server_init(ctxt, ms);
  26. +      if (mcm_server_connect((struct memcache_ctxt*)ctxt, mc, ms) != -1) {
  27. +        mcm_server_activate(ctxt, mc, ms);
  28. +      }
  29.        continue;
  30.      } else {
  31.        MCM_ERRX(MCM_ERR_ASSERT);

これで libmemcache.so を作って上の mctest を動かしつつ、memcached を kill してやると、mctest の出力的にはこんな感じ。

CODE:
  1. ...
  2. k29694 : v29694
  3. k29695 : v29695
  4. [NOTICE@1186114595.351118] mcm_server_connect():2299: Software caused connection abort
  5. k29696 : v29696
  6. k29697 : v29697
  7. [NOTICE@1186114595.351893] mcm_server_connect():2299: Software caused connection abort
  8. k29698 : v29698
  9. k29699 : v29699
  10. ...

kXXXXX : vXXXXX は mctest の出力なのでここでは無視するとして、NOTICE 行は落ちているサーバに接続に行って復旧を試みているところ。で、kill した memcached をもう 1 度立ち上げてやると、ちょっと待てばそのサーバにも接続できるようになって、この NOTICE 行は出なくなります。

このパッチを当てると、libmemcache が exit することもなくなり、サーバの障害で抜いたり戻したりも意識しないでできるようになります。てかそもそもそうであるべきだよねって話なんですけどね。

というわけで、誰かの役に立てばいいなあということで、libmemcache の作者の方に上のパッチを送ってみようと思います。


About this entry