2

タイムアウトを含む特定のサイズに成長するファイル (fd を指定) を監視する関数を作成しました。kqueue()/を使用kevent()してファイルが「拡張」されるのを待ちますが、ファイルが大きくなったという通知を受け取った後、ファイルサイズを確認する必要があります (そして、それを目的のサイズと比較します)。それは簡単に思えますが、POSIXで確実に行う方法がわかりません。

注意: 指定された時間内にファイルがまったく拡張されない場合、タイムアウトが発生します。そのため、これは絶対的なタイムアウトではなく、ファイルのサイズが大きくなるタイムアウトです。kevent()私はOS Xを使用していますが、この質問は「 /を持つすべてのPOSIX」を対象kqueue()としています。これは、OS Xと私が考えるBSDである必要があります。

私の関数の現在のバージョンは次のとおりです。

/**
 * Blocks until `fd` reaches `size`. Times out if `fd` isn't extended for `timeout`
 * amount of time. Returns `-1` and sets `errno` to `EFBIG` should the file be bigger
 * than wanted.
 */
int fwait_file_size(int fd,
                    off_t size,
                    const struct timespec *restrict timeout)
{
    int ret = -1;
    int kq = kqueue();
    struct kevent changelist[1];

    if (kq < 0) {
        /* errno set by kqueue */
        ret = -1;
        goto out;
    }

    memset(changelist, 0, sizeof(changelist));
    EV_SET(&changelist[0], fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_RENAME | NOTE_EXTEND, 0, 0);

    if (kevent(kq, changelist, 1, NULL, 0, NULL) < 0) {
        /* errno set by kevent */
        ret = -1;
        goto out;
    }

    while (true) {
        {
            /* Step 1: Check the size */
            int suc_sz = evaluate_fd_size(fd, size); /* IMPLEMENTATION OF THIS IS THE QUESTION */
            if (suc_sz > 0) {
                /* wanted size */
                ret = 0;
                goto out;
            } else if (suc_sz < 0) {
                /* errno and return code already set */
                ret = -1;
                goto out;
            }
        }

        {
            /* Step 2: Wait for growth */
            int suc_kev = kevent(kq, NULL, 0, changelist, 1, timeout);

            if (0 == suc_kev) {
                /* That's a timeout */
                errno = ETIMEDOUT;
                ret = -1;
                goto out;
            } else if (suc_kev > 0) {
                if (changelist[0].filter == EVFILT_VNODE) {
                    if (changelist[0].fflags & NOTE_RENAME || changelist[0].fflags & NOTE_DELETE) {
                        /* file was deleted, renamed, ... */
                        errno = ENOENT;
                        ret = -1;
                        goto out;
                    }
                }
            } else {
                /* errno set by kevent */
                ret = -1;
                goto out;
            }
        }
    }

    out: {
        int errno_save = errno;
        if (kq >= 0) {
            close(kq);
        }
        errno = errno_save;
        return ret;
    }
}

したがって、基本的なアルゴリズムは次のように機能します。

  1. ケイベントをセットアップする
  2. サイズを確認
  3. ファイルの増大を待つ

ファイルが目的のサイズに達するまで、手順 2 と 3 が繰り返されます。

コードは、「何らかのエラーが発生したか、ファイルが必要以上に大きい」、「ファイルがまだ十分に大きくない」、またはファイルが必要なサイズに達した場合int evaluate_fd_size(int fd, off_t wanted_size)に返す関数を使用します。< 0== 0> 0

明らかに、これevaluate_fd_sizeはファイル サイズの決定において信頼できる場合にのみ機能します。私の最初の試みは、それを で実装してoff_t eof_pos = lseek(fd, 0, SEEK_END)比較するeof_posことでしたwanted_size。残念ながら、lseek結果をキャッシュしているようです。keventで返してもNOTE_EXTEND、ファイルが大きくなったので、結果は同じかもしれません!次に、に切り替えようと思ったのですfstatが、キャッシュもある記事を見つけましfstatた。

私が最後に試したのはfsync(fd);以前の使用でしたがoff_t eof_pos = lseek(fd, 0, SEEK_END);、突然機能し始めました。しかし:

  1. fsync()私の問題を本当に解決するものは何もありません
  2. fsync()パフォーマンスのためにしたくない

編集: 再現するのは本当に難しいですが、fsync()役に立たないケースが 1 つあります。NOTE_EXTENDイベントがユーザー空間にヒットした後、ファイル サイズが大きくなるまで (ごくわずか) 時間がかかるようです。fsync()おそらく十分sleep()に機能するため、ほとんどの場合機能します:-。

言い換えれば、ファイル名がわからないためにできないファイルを開いたり閉じたりせずに、POSIXでファイルサイズを確実にチェックする方法。さらに、これが役立つという保証を見つけることができません

ちなみに、int new_fd = dup(fd); off_t eof_pos = lseek(new_fd, 0, SEEK_END); close(new_fd);キャッシングの問題は解決していません。

EDIT 2 :オールインワンのデモ プログラムも作成しました。終了する前に印刷された場合Ok, success、すべてがうまくいきました。ただし、通常Timeout (10000000)は競合状態を示す出力が表示されます。最後にトリガーされた kevent のファイル サイズ チェックは、現時点での実際のファイル サイズよりも小さくなっています。奇妙なことに、 を使用ftruncate()してファイルを拡大するのではなく、write()動作しているように見えます (テスト プログラムをコンパイルしてテストすることができます-DUSE_FTRUNCATE)。

4

1 に答える 1