Issue with multithreaded/concurrent use of ReadFile() Windows API

3 weeks ago 26
ARTICLE AD BOX

You are using the OVERLAPPED incorrectly. You need to use an event object in its hEvent field. This is required if you want to have multiple threads reading from the same disk handle at the same time. Otherwise, completion status of a read will be stored in the disk handle itself, and multiple reads in parallel will step over each other's status, confusing GetOverlappedResult().

Per Microsoft's documentation:

Synchronization and Overlapped Input and Output

An event is needed only if there will be more than one outstanding I/O operation at the same time. If an event is not used, each completed I/O operation will signal the file, named pipe, or communications device.

GetOverlappedResult function

If the hEvent member of the OVERLAPPED structure is NULL, the system uses the state of the hFile handle to signal when the operation has been completed. Use of file, named pipe, or communications-device handles for this purpose is discouraged. It is safer to use an event object because of the confusion that can occur when multiple simultaneous overlapped operations are performed on the same file, named pipe, or communications device. In this situation, there is no way to know which operation caused the object's state to be signaled.

Also, read != size does not mean EOF was reached. That is a mistake in your error handling. If the read is successful, it can return fewer bytes than requested. For a synchronous read, EOF is signaled by read == 0 when ReadFile() returns TRUE. But for an asynchronous read, EOF is signaled by GetOverlappedResult() failing with the ERROR_HANDLE_EOF error code. This is explained in the documentation: Testing for the End of a File. Either way, if you need the full size to be read then call ReadFile() in a loop until all requested bytes are actually read.

With that said, try this instead:

#include <Windows.h> #include <cstdint> #include <expected> #include <string> #include <thread> #include "mt_reader.hpp" #include "Utils/error.hpp" mt_reader::mt_reader(std::wstring volume_name) { if (volume_name.back() == L'\\') { volume_name.pop_back(); } _handle_disk = CreateFileW( volume_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr ); if (_handle_disk == INVALID_HANDLE_VALUE) { auto err = win_error(GetLastError(),ERROR_LOC); win_error::print(err); } else { auto temp_read = read(0, _boot_record, 0x200, 'a'); if (!temp_read.has_value()) { win_error::print(temp_read.error().add_to_error_stack("Caller: read error", ERROR_LOC)); } } } mt_reader::~mt_reader() { std::cout << "mt_reader closed" << std::endl; if (_handle_disk != INVALID_HANDLE_VALUE) { if (!CloseHandle(_handle_disk)) { auto err = win_error(GetLastError(),ERROR_LOC); win_error::print(err); } } else { std::cout << win_error("Invalid Handle Value", ERROR_LOC); } } std::expected<void, win_error> mt_reader::read(uint64_t offset, void *buf, uint32_t size) { OVERLAPPED ov{}; ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!ov.hEvent) { return std::unexpected(win_error(GetLastError(), ERROR_LOC)); } BYTE *pbuf = static_cast<BYTE*>(buf); DWORD read; while (size > 0) { ov.Offset = static_cast<DWORD>(offset & 0xffffffff); ov.OffsetHigh = static_cast<DWORD>(offset >> 32); read = 0; if (BOOL ok = ReadFile( _handle_disk, pbuf, size, nullptr, &ov ); !ok) { DWORD err = GetLastError(); if (err != ERROR_IO_PENDING) { CloseHandle(ov.hEvent); return std::unexpected(win_error(err, ERROR_LOC)); } ok = GetOverlappedResult( _handle_disk, &ov, &read, TRUE ); if (!ok) { err = GetLastError(); CloseHandle(ov.hEvent); return std::unexpected(win_error(err, ERROR_LOC)); } } else { read = ov.InternalHigh; } if (read == 0) { CloseHandle(ov.hEvent); return std::unexpected(win_error(ERROR_HANDLE_EOF, ERROR_LOC)); } offset += read; size -= read; pbuf += read; } CloseHandle(ov.hEvent); return {}; }
Read Entire Article