0

I created an overlapped WriteFile scheme, where one thread fills a ring-buffer and advances the head-pointer and calls WriteFile, and another thread watches events from OVERLAPPED writes to advance the tail pointer. The logic is behaving as expected, but the file size is not increasing, it remains the same and just over-writes from position 0. I tested this by writing incremental value into the memory written to the file, and the index data increases, but it keeps writing at what is effectively fseek(0).

Here is the code.

First I open the file as GENERIC_WRITE and FILE_FLAG_OVERLAPPED, and I create 8 events, one for each page I am going to write. Each page is 256KBytes. I was originally using just one event, but I wanted to verify that the # of events wasn't the problem.

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);
    }
}

Now in another thread I call this queue_page function whenever a buffer fills (8 x 256K pages). m_ov is an array of 8 OVERLAPPED structures. Prior to this call, the m_pages[][] acts as a ring buffer, and the m_pages[m_head][] is advanced, sending down the m_pages[old page][] to write to disk.

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");
        }
    }
}

The thread that follows the async writes and moves the circular-buffer tail pointer is simple:

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;
    }
}

Upon inspection the ring buffer is working fine and not overflowing, but the output file is always 256KBytes.

Not sure how to debug what is going on, or what I missed.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
PeterT
  • 920
  • 8
  • 20
  • 2
    you need pass file offset by self in every read and write. but you by self pass 0 here – RbMm Feb 15 '21 at 00:39
  • as side note - using events here - the worst option of all – RbMm Feb 15 '21 at 00:44
  • I can't believe I completely misunderstood the page on OVERLAPPED. Thanks, problem solved. If you make this an answer I'll check it. Why is events the worst option? (I tried using FileWriteEx for it's cool callback, but since my callbacks are bound member functions, I didn't want to fuss with stdc++ bindings) – PeterT Feb 15 '21 at 00:46
  • *but since my callbacks are bound member functions* - and so what ? you have content in `OVERLAPPED`. and easy possibly bind this to class. or use `NtWriteFile` - this more easy and comfortably for use/pass context. or possible bind file to iocp, direct or indirect (say via *BindIoCompletionCallback*). apc or iopc much more better than worst events – RbMm Feb 15 '21 at 00:50
  • Unfortunately, I didn't understand what you are saying in the rest of your response. Can you rephrase it? content in overlapped? Comfortably pass? bind file to iocp? What? – PeterT Feb 15 '21 at 00:54
  • 1
    exist in 3 ways get notify when I/O completed - events, apc and iocp notification. apc and iocp - let you pass addition context to I/O - and when I/O finished - you got this context back. but events not let pass additional context. all what you got - number or event in array passed to wait function. `page = ret - WAIT_OBJECT_0` here `page` you "context" and how you got it. really very not comfortable use events compare apc and iocp. you can say put in `OVERLAPPED` pointer to your class, page, additional operation info - and got in back when I/O finished. and not need use `FileWriter::wait` – RbMm Feb 15 '21 at 01:02
  • I understand. Thanks for the tips. – PeterT Feb 15 '21 at 02:33
  • Don't edit the answer into your question. If you found an answer post it as an answer instead. That allows future visitors to easily find answered questions. – IInspectable Feb 15 '21 at 07:54

1 Answers1

0

Answer from @RbMm, byte-access files require the caller to set Offset/OffsetHigh in the overlapped structure, according to this: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped

Fix looks like this:

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);
}
PeterT
  • 920
  • 8
  • 20