Почему WriteFile не увеличивает смещение файла для последующих операций записи в этом коде?

Я создал схему WriteFile с перекрытием, в которой один поток заполняет кольцевой буфер, перемещает указатель начала и вызывает WriteFile, а другой поток наблюдает за событиями записи OVERLAPPED для перемещения указателя хвоста. Логика ведёт себя как положено, но размер файла не увеличивается, он остаётся прежним и просто перезаписывает с позиции 0. Я проверял это, записывая инкрементное значение в память, записываемую в файл, и данные индекса увеличиваются, но он продолжает писать то, что эффективно fseek(0).

Вот код.

Сначала я открываю файл как GENERIC_WRITE и FILE_FLAG_OVERLAPPED и создаю 8 событий, по одному для каждой страницы, которую я собираюсь написать. Каждая страница имеет размер 256 КБ. Изначально я использовал только одно событие, но хотел убедиться, что проблема не в количестве событий.

void
FileWriter::open(string fn)
{
    cout << "Opening file " << fn << endl;
    m_file_handle = CreateFileA(
        fn.c_str(),
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_FLAG_OVERLAPPED,
        NULL);
    if (m_file_handle == INVALID_HANDLE_VALUE)
    {
        throw runtime_error("Unable to create FileWriter file handle");
    }
    for (size_t i = 0; i < MAX_OVERLAPPED_WRITES; ++i)
    {
        m_events[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
    }
}

Теперь в другом потоке я вызываю эту функцию queue_page всякий раз, когда заполняется буфер (8 x 256K страниц). m_ov — это массив из 8 ПЕРЕКРЫТЫХ структур. До этого вызова m_pages[][] действует как кольцевой буфер, а m_pages[m_head][] расширяется, отправляя m_pages[old page][] для записи на диск.

void
FileWriter::queue_page(unsigned page, unsigned len)
{
    ZeroMemory(&m_ov[page], sizeof(OVERLAPPED));
    m_ov[page].hEvent = m_events[page];
    if (!WriteFile(
        m_file_handle,
        (LPCVOID)m_pages[page],
        len * sizeof(float),
        NULL,
        (LPOVERLAPPED)&m_ov[page]))
    {
        DWORD err = GetLastError();
        if (err != ERROR_IO_PENDING)
        {
            cout << "GetLastError() = " << err << endl;
            throw runtime_error("Failed to write overlapped");
        }
    }
}

Поток, который следует за асинхронной записью и перемещением указателя хвоста циклического буфера, прост:

void
FileWriter::wait(DWORD msec)
{
    DWORD ret = WaitForMultipleObjects(MAX_OVERLAPPED_WRITES, m_events, FALSE, msec);
    if (ret < MAXIMUM_WAIT_OBJECTS)
    {
        unsigned page =  ret - WAIT_OBJECT_0;
        if (page < MAX_OVERLAPPED_WRITES) {
            ResetEvent(m_events[page]);
        }
        cout << "Page write completed " << ret << " page=" << page << endl;
        m_tail = (m_tail + 1) & 0x7;
    }
}

При проверке кольцевой буфер работает нормально и не переполняется, но выходной файл всегда имеет размер 256 КБ.

Не уверен, как отлаживать то, что происходит, или то, что я пропустил.


person PeterT    schedule 15.02.2021    source источник
comment
вам нужно передать смещение файла самостоятельно при каждом чтении и записи. но вы сами передаете 0 здесь   -  person RbMm    schedule 15.02.2021
comment
в качестве примечания - использование событий здесь - худший вариант из всех   -  person RbMm    schedule 15.02.2021
comment
Я не могу поверить, что совершенно неправильно понял страницу на OVERLAPPED. Спасибо, проблема решена. Если вы сделаете это ответом, я проверю его. Почему события — худший вариант? (Я пытался использовать FileWriteEx для его классного обратного вызова, но, поскольку мои обратные вызовы являются связанными функциями-членами, я не хотел возиться с привязками stdc++)   -  person PeterT    schedule 15.02.2021
comment
но так как мои обратные вызовы являются связанными функциями-членами - и что с того? у вас есть контент в OVERLAPPED. и легко привязать это к классу. или используйте NtWriteFile - это более просто и удобно для использования/передачи контекста. или возможная привязка файла к iocp, прямо или косвенно (скажем, через BindIoCompletionCallback). apc или iopc намного лучше, чем худшие события   -  person RbMm    schedule 15.02.2021
comment
К сожалению, я не понял, что вы говорите в остальной части вашего ответа. Можешь перефразировать? содержимое перекрывается? Комфортно пройти? привязать файл к iocp? Какие?   -  person PeterT    schedule 15.02.2021
comment
существует 3 способа получения уведомления о завершении ввода-вывода - события, уведомление apc и iocp. apc и iocp — позволяют передать дополнительный контекст в ввод-вывод — и когда ввод-вывод завершится — вы получите этот контекст обратно. но события не пропускают дополнительный контекст. все, что у вас есть - число или событие в массиве, переданном функции ожидания. page = ret - WAIT_OBJECT_0 здесь page ваш контекст и как вы его получили. действительно очень не удобно пользоваться событиями сравнивая apc и iocp. вы можете сказать, что вставили указатель OVERLAPPED на свой класс, страницу, дополнительную информацию об операции - и вернулись, когда ввод-вывод завершился. и не нужно использовать FileWriter::wait   -  person RbMm    schedule 15.02.2021
comment
Я понимаю. Спасибо за советы.   -  person PeterT    schedule 15.02.2021
comment
Не редактируйте ответ в свой вопрос. Если вы нашли ответ, опубликуйте его как ответ. Это позволяет будущим посетителям легко находить ответы на вопросы.   -  person IInspectable    schedule 15.02.2021


Ответы (1)


Ответ от @RbMm, файлы с доступом к байтам требуют, чтобы вызывающая сторона установила Offset/OffsetHigh в перекрывающейся структуре, в соответствии с этим: https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped

Фикс выглядит так:

void
FileWriter::queue_page(unsigned page, unsigned len)
{
    ZeroMemory(&m_ov[page], sizeof(OVERLAPPED));
    m_ov[page].hEvent = m_events[page];
    m_ov[page].OffsetHigh = (m_file_offset >> 32) & 0xFFFF'FFFF;
    m_ov[page].Offset = m_file_offset & 0xFFFF'FFFF;
    if (!WriteFile(
        m_file_handle,
        (LPCVOID)m_pages[page],
        len * sizeof(float),
        NULL,
        (LPOVERLAPPED)&m_ov[page]))
    {
        DWORD err = GetLastError();
        if (err != ERROR_IO_PENDING)
        {
            cout << "GetLastError() = " << err << endl;
            throw runtime_error("Failed to write overlapped");
        }
    }
    m_file_offset += len * sizeof(float);
}
person PeterT    schedule 16.02.2021