neoagent1人読書会 その6


今回はリクエストをKVSに書き出す部分。na_target_server_callbackのWRITEを読む。

```c
static void na_target_server_callback (EV_P_ struct ev_io *w, int revents)
{
    int cfd, tsfd, size;
    na_client_t *client;
    na_env_t *env;

    tsfd   = w->fd;
    client = (na_client_t *)w->data;
    env    = client->env;
    cfd    = client->cfd;

    pthread_rwlock_rdlock(&LockReconf);
    pthread_rwlock_rdlock(&env->lock_refused);
    if ((client->is_refused_active != env->is_refused_active) || env->is_refused_accept) {
        pthread_rwlock_unlock(&env->lock_refused);
        NA_EVENT_FAIL(NA_ERROR_INVALID_CONNPOOL, EV_A, w, client, env);
        goto unlock_reconf; // request fail
    }
    pthread_rwlock_unlock(&env->lock_refused);

    if (env->loop_max > 0 && client->loop_cnt++ > env->loop_max) {
        NA_EVENT_FAIL(NA_ERROR_OUTOF_LOOP, EV_A, w, client, env);
        goto unlock_reconf; // request fail
    }

    if (revents & EV_READ) {

..

    } else if (revents & EV_WRITE) {
```

まずはいつものようにコールバック関数が始まる。revents & EV_READの部分は次回にまわして、else if以降を見ていく。

```c
    } else if (revents & EV_WRITE) {

        size = write(tsfd, 
                     client->crbuf + client->swbufsize, 
                     client->crbufsize - client->swbufsize);
```

writeは第三引数に与えた整数バイト以下のバイト数を書き出す。

- crbuf: リクエストバッファーのバイト数
- crbufsize: リクエストバッファーのうち実際に使われているバイト数
- swbufsize: リクエストバッファーのうち書き出し完了したバイト数

```c
        if (size < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
                return; // not ready yet
            } else if (client->is_use_connpool) {
                int i = client->cur_pool;
                na_server_t *server;
                pthread_mutex_lock(&env->lock_connpool);
                if (client->connpool->fd_pool[i] > 0) {
                    close(client->connpool->fd_pool[i]);
                }
```

書き出し失敗した場合、コネクションプールのコネクションだったらclose。

```c
                client->connpool->fd_pool[i] = na_target_server_tcpsock_init();
                if (env->is_use_backup) {
                    pthread_rwlock_rdlock(&env->lock_refused);
                    server = env->is_refused_active ? &env->backup_server : &env->target_server;
                    pthread_rwlock_unlock(&env->lock_refused);
                } else {
                    server = &env->target_server;
                }
                na_target_server_tcpsock_setup(client->connpool->fd_pool[i], true);
                if (client->connpool->fd_pool[i] <= 0) {
                    pthread_mutex_unlock(&env->lock_connpool);
                    NA_DIE_WITH_ERROR(NA_ERROR_INVALID_FD);
                }

                if (!na_server_connect(client->connpool->fd_pool[i], &server->addr)) {
                    if (errno != EINPROGRESS && errno != EALREADY) {
                        pthread_mutex_unlock(&env->lock_connpool);
                        na_error_count_up(env);
                        NA_DIE_WITH_ERROR(NA_ERROR_CONNECTION_FAILED);
                    }
                }
                pthread_mutex_unlock(&env->lock_connpool);
```

その後、ソケットを開き直して接続を確立する。次も接続できなければエラー吐いて終了。

NA_DIE_WITH_ERRORはexitするので、悠長にna_error_count_upとかしなくてもいいような気がする。

```c
            if (errno == EPIPE) {
                NA_EVENT_FAIL(NA_ERROR_BROKEN_PIPE, EV_A, w, client, env);
            } else {
                NA_EVENT_FAIL(NA_ERROR_FAILED_WRITE, EV_A, w, client, env);
            }
            return; // request fail
        }
```

SIGPIPEエラーのとき（相手がすでにcloseしていた場合）はこのイベントだけ失敗する。

ちなみにSIGPIPEはneoagent.cのna_setup_signalsでsigignore(SIGPIPE)されているので落ちることはない。

しかしここで見ているerrnoはwriteのエラーだけを検知したいんだろうか？client->is_use_connpool節の中のエラーに変わってしまっている場合もあるんじゃないだろうか？

いずれにせよ、書き出し失敗した場合はこのリクエストは終了。

```c
        client->swbufsize += size;

        if (client->swbufsize < client->crbufsize) {
            na_event_switch(EV_A_ w, &client->ts_watcher, tsfd, EV_WRITE);
        } else {
            client->event_state = NA_EVENT_STATE_TARGET_READ;
            na_event_switch(EV_A_ w, &client->ts_watcher, tsfd, EV_READ);
        }
        return;
    }
```

既に書き出し終わった場合はクライアントの状態をNA_EVENT_STATE_TARGET_READにして、最後にna_event_switchを呼び出してソケットへの読み込みに移っている。

書き出しが終わってない場合にもna_event_switchを呼んでいる理由はよくわからない。

ところで、失敗した場合などにclient->is_usedをfalseにする処理はどこにあるのかと思って追ってみたら、NA_EVENT_FAILマクロの中でna_event_stopというのが呼ばれてて、さらにその中でna_client_closeというのが呼ばれてた。na_client_closeでは、クライアントを未使用にする他にもイベントウォッチャーを外したりソケットを未使用にしたりしている。

次回はna_target_server_callbackのREADを読む。