tokyo tyrantのSolaris対応(event ports)を書き直してみた

前回の記事の対応ではスピードが出ません。
というわけでちょっとport_getnの所を修正しました。
make checkでそこそこの速度で全部通るようになりました。

あ、補足ですがtokyo cabinetの最新版はSolaris10で何も指定しなくても普通に通るようになりました。

今回対象にしたのは、tokyotyrant-1.1.14です。(今日付で最新版が出ていますが対応は基本的に同じでいけると思います)

開発環境はSolaris10 x86_64です。uname -aの結果は、
「SunOS tamsvr11 5.10 Generic_137138-09 i86pc i386 i86pc Solaris」です。

というわけで以下修正ソースです。

 

修正対象のファイルはmyconf.h, myconf.c, ttutil.cです。

まずはmyconf.hの修正点です。

94行目付近(SunOSも対応を表明)

#if !defined(_SYS_LINUX_) && !defined(_SYS_FREEBSD_) && !defined(_SYS_MACOSX_) && !defined(_SYS_SUNOS_)
#error =======================================
#error Your platform is not supported.  Sorry.
#error =======================================
#endif

214行目付近(SunOS用のマクロ追加とKQUEUE使わない事を明示)

#if defined(_SYS_FREEBSD_) || defined(_SYS_MACOSX_)
#define TTUSEKQUEUE    1
#elif defined(_SYS_SUNOS_)
#include <sys/loadavg.h>
#define TTUSEKQUEUE 0
#define TTUSESOLEVENTPORTS 1
#else
#include <sys/epoll.h>
#define TTUSEKQUEUE    0
#endif

262行目付近(kqueue用の定義したものをSunOSでもそのまま拝借する)

#if TTUSEKQUEUE || TTUSESOLEVENTPORTS

294行目付近(#define epoll_wait の後の#endifの外側)
これはttutil.cに差し込むための処理

#if TTUSESOLEVENTPORTS
#define TTREASSOC(lfd) \
    do { \
        struct epoll_event ev; \
        ev.events = EPOLLIN; \
        ev.data.fd = lfd; \
        if (epoll_ctl(epfd, EPOLL_CTL_MOD, lfd, &ev) != 0) { \
            cfd = -1; \
        } \
    } while (false);
#else
#define TTREASSOC(lfd)
#endif

次はmyconf.cへ処理を追加します。
myconf.cの最後に以下の定義を追加します。
abortとか危ない関数使っていますが、何かのバージョンアップでここに処理が入るようになった場合に下手に動いてしまうよりも未実装の部分なので落としてしまった方が良いという考えから入れました。

#if TTUSESOLEVENTPORTS
#include <port.h>

int _tt_epoll_create(int size) {
    int port = port_create();
    if (port < 0) {
        perror("port_create failure");
        return -1;
    }
    return port;
}

int _tt_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) {
    if (op == EPOLL_CTL_ADD || op == EPOLL_CTL_MOD) {
        if (event->events & EPOLLIN) {
            int result = port_associate(epfd, PORT_SOURCE_FD, fd, POLLIN, event);
            if (result == -1) {
                perror("port_associate failure");
            }
            return result;
        } else {
            fprintf(stderr, "dont support\n");
            abort();
        }
    } else if (op == EPOLL_CTL_DEL) {
        int result = port_dissociate(epfd, PORT_SOURCE_FD, fd);
        if (result == -1) {
            perror("port_dissociate failure");
            return -1;
        }
        return 0;
    }
    return -1;
}

int _tt_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) {
    port_event_t list[maxevents];
    struct timespec ts;

    div_t td = div(timeout, 1000);
    ts.tv_sec = td.quot;
    ts.tv_nsec = td.rem * 1000000;

    uint_t actevents = maxevents;
    if(port_getn(epfd, list, 0, &actevents, &ts) == -1) {
        perror("FIRST-port_getn failure");
        return -1;
    }
    if (actevents == 0) {
        actevents = 1;
    }

    if (actevents > maxevents) actevents = maxevents;
    if (port_getn(epfd, list, maxevents, &actevents, &ts) == -1) {
        if (errno == EINTR) {
            return 0;
        } else if (errno != ETIME) {
            perror("port_getn failure");
            return -1;
        }
    }
    for (int i = 0; i < actevents; i++) {
        events[i].data.fd = list[i].portev_object;
    }
    return actevents;
}

#endif

次はttutil.cにマクロを1つ定義します。
#ifdefを外に出した感じです。関数にしてしまうと、Sun以外の時に無駄なダミー関数が走る事になるので、マクロにしました。 
↓の行のTTREASSOC(lfd);が追加したものです。

          if(serv->port < 1){
            cfd = ttacceptsockunix(lfd);
            sprintf(addr, "(unix)");
            port = 0;
          } else {
            cfd = ttacceptsock(lfd, addr, &port);
            fprintf(stderr, "++accept\n");
          }
          TTREASSOC(lfd); // ここに追加
          if(cfd != -1){

そんでもってconfigureには"-D__EXTENSIONS__ -D_REENTRANT"を指定すればビルドできます。なお、ldがsonameを正しく解釈できなくて「-oが2個定義されているよ」みたいなエラーが出た場合は、 Makefileを以下のようにして削ってしまいます。

libtokyotyrant.so.$(LIBVER).$(LIBREV).0 : $(LIBOBJFILES)
        $(CC) -shared -o $@ $(LIBOBJFILES) \
          $(LDFLAGS) $(LIBS)

さて、ちょっと気になったことがあります。Client側を強制終了させるとサーバ側で「close failed」のエラーログが出力されます。 ちょっとソースを追ってみたところ、EPOLL_CTL_DELを発行した後のttclosesockで発生しているのですが、どうもperrorの内容とerrnoの内容が一致しないのです。その部分のソースは↓になるのですが、

/* Shutdown and close a socket. */
bool ttclosesock(int fd){
  assert(fd >= 0);
  bool err = false;
  if(shutdown(fd, 2) != 0 && errno != ENOTCONN && errno != ECONNRESET) err = true;
  if(close(fd) != 0 && errno != ENOTCONN && errno != ECONNRESET) err = true;
  return !err;
}

perrorで表示される内容は「Transport endpoint is not connected」と表示されるのでECONNRESETにあたる内容なのですが、errnoは99や62を返してきます。。。ちなみに値を確認したときは↑のコードを分解して直後に表示させました。なので他の関数が書き換えているというわけではなさそうです。また、shutdownの直前でerrno=0としてもperrorではECONNRESETのメッセージを表示します。

つまり、perrorの内部では正しいエラー番号を取得できているのに、この関数の中では別の場所のerrnoを指しているんじゃないかと思うわけです。(gdbで変数のアドレスを見たわけではないですけど・・・)。

ここら辺ってSolarisの場合はスレッドセーフ、というかerrnoはスレッド毎に値を持っている(スレッドローカル)わけなので、別の所を参照するって意味がわからないのです(´∩ω∩`)

誰か分かる人居ませんかねぇ・・。

追記 2009/02/16 23:40

-D_REENTRANTを指定していないのが原因みたいです。Sunのマニュアルにおもいっきり書いてありました。以下引用。

マルチスレッドアプリケーションを使用する場合は、コンパイル時にコマンド行で_REENTRANT フラグを定義します (-D_REENTRANT)。_REENTRANT フラグを定義すると、errno はマクロになり、スレッドごとに個別の errno を保持できるようになります。この errno マクロは、変数と同じように、代入文の左辺と右辺のどちらにも使用できます。
新しいサイトもよろしくお願いします!