winapioverlapped-iogetoverlappedresult

Using the timeout in GetOverlappedResultEx to simulate a wait with timeout?


When using GetOverlapedResult to get the result of an overlapped (i.e. asynchronous) I/O operation, you can ask GetOverlappdResult to "wait":

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

The part to note here is the final parameter to GetOverlappedResult: bWait:

bWait

If this parameter is TRUE, and the Internal member of the lpOverlapped structure is STATUS_PENDING, the function does not return until the operation has been completed. If this parameter is FALSE and the operation is still pending, the function returns FALSE and the GetLastError function returns ERROR_IO_INCOMPLETE.

For my code this means:

And this all works great.

Mostly this all works great

I'm having an issue where the actual ReadFile operation is taking ten minutes to return. When it does return, it returns:

The device is not ready (21)

It's a well-known problem with this one vendor's storage system..

What i would like to do is use the asynchronous capabilities of Windows to wait - but with a timeout.

And i noticed GetOverlappedResultEx, it has a sort of timeout parameter:

BOOL GetOverlappedResultEx(
  HANDLE       hFile,
  LPOVERLAPPED lpOverlapped,
  LPDWORD      lpNumberOfBytesTransferred,
  DWORD        dwMilliseconds, <----------
  BOOL         bAlertable
);

But when i look at the documentation, this is where i get into details of Windows programming i don't understand - queued APCs, alterable waits. But it still sounds like what i want::

dwMilliseconds

The time-out interval, in milliseconds.

If dwMilliseconds is zero and the operation is still in progress, the function returns immediately and the GetLastError function returns ERROR_IO_INCOMPLETE.

If dwMilliseconds is nonzero and the operation is still in progress, the function waits until the object is signaled, an I/O completion routine or APC is queued, or the interval elapses before returning. Use GetLastError to get extended error information.

If dwMilliseconds is INFINITE, the function returns only when the object is signaled or an I/O completion routine or APC is queued.

So i try changing my function:

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      //if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, False) //wait 5000 ms
         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

Except the call to GetOverlappedResultEx doesn't return in 5 seconds (5,000 ms). Instead it continues to take 10-20 minutes for the storage subsystem to get around to returning a failure.

So i randomly try things

I see another parameter of GetOverlappedResultsEx:

`bAlertable`

If this parameter is **TRUE** and the calling thread is in the waiting state, the function returns when the system queues an I/O completion routine or APC. The calling thread then runs the routine or function. Otherwise, the function does not return, and the completion routine or APC function is not executed.

A completion routine is queued when the [ReadFileEx][5] or [WriteFileEx][5] function in which it was specified has completed. The function returns and the completion routine is called only if *bAlertable* is **TRUE**, and the calling thread is the thread that initiated the read or write operation. An APC is queued when you call [QueueUserAPC][5].

This doesn't sound like my situation:

But that doesn't have to stop me from randomly trying things and hope they work:

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      //if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, False) //wait 5000 ms
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, True) //wait 5000 ms, alertable

         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

But it doesn't work.

So i ask Stackoverflow

Can i simulate a synchronous ReadFile operation but with a timeout, using GetOverlappedResultEx?

I'm sure i could eventually get a buggy hack involving messaging timers (or is that thread timers? Or is that alertable timers? and events? and my own events? Or the event in the overlap?). But i'd rather use the good solution rather than my solution.


Solution

  • Can i simulate a synchronous ReadFile operation but with a timeout, using GetOverlappedResultEx?

    yes, you can, exactly like you and try already. and this is not simulation. this will be exactly synchronous file read. because synchronous read - this is asynchronous read + wait in place when I/O complete. so code can be next:

    ULONG ReadFileTimeout(HANDLE hFile, 
                   PVOID lpBuffer, 
                   ULONG nNumberOfBytesToRead, 
                   PULONG lpNumberOfBytesRead, 
                   PLARGE_INTEGER ByteOffset, 
                   ULONG dwMilliseconds)
    {
        OVERLAPPED ov;
        ov.Offset = ByteOffset->LowPart;
        ov.OffsetHigh = ByteOffset->HighPart;
    
        ULONG dwError = NOERROR;
    
        if (ov.hEvent = CreateEvent(0, 0, 0, 0))
        {
            dwError = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &ov) ? NOERROR : GetLastError();
    
            if (dwError == ERROR_IO_PENDING)
            {
                dwError = GetOverlappedResultEx(0/*yes, not need hFile*/, 
                    &ov, lpNumberOfBytesRead, dwMilliseconds, FALSE) ? NOERROR : GetLastError();
            }
    
            CloseHandle(ov.hEvent);
        }
        else
        {
            dwError = GetLastError();
        }
    
        return dwError;
    }
    

    note that you must (in case asynchronous file) always explicit use Byte offset, from where read data.

    the GetOverlappedResultEx in general do next - check the OVERLAPPED - are I/O complete (simply compare Internal with STATUS_PENDING), and if not ( still Internal == STATUS_PENDING) and you want wait - it call WaitForSingleObjectEx with hEvent from OVERLAPPED and transferred dwMilliseconds, and bAlertable. if hEvent signaled (WAIT_OBJECT_0 returned from WaitForSingleObjectEx) GetOverlappedResultEx decide that I/O complete and return result from OVERLAPPED, otherwise appropriate error returned.

    but in case the ReadFile itself not return control and wait - here impossible do something. this mean that driver begin wait on your thread. and no any way break this driver wait. look like this exactly your case. when you use asynchronous file - driver must not wait, but .. sometimes (bad written driver) this can be