Hello @drjackool ,
Thank you for the details. I spent some time looking into how InternetReadFileEx behaves with the IRF_NO_WAIT flag, and I would suggest avoiding both approaches you mentioned. A busy loop wastes CPU, and a Sleep()-and-retry loop is not really reliable either, since there is no correct value to sleep for. The way WinINet is meant to be used here is event-driven, so instead of guessing a delay you let the library tell you when data has arrived.
The important thing I noticed is that IRF_NO_WAIT is only meaningful on an asynchronous handle, that is, a session you opened with INTERNET_FLAG_ASYNC and for which you registered a status callback through InternetSetStatusCallback. If the handle is synchronous, the flag does not actually surface a "data not ready" state, so it does not solve the blocking you are currently seeing. On an asynchronous handle the behavior becomes what you described: when some data is already buffered the call returns TRUE with the bytes right away, and when nothing is available yet it returns FALSE with GetLastError() equal to ERROR_IO_PENDING (997).
One detail that is easy to miss is that with IRF_NO_WAIT the ERROR_IO_PENDING return does not fill your buffer. This is different from a normal asynchronous read without the flag, where the bytes are delivered into your INTERNET_BUFFERS when the completion callback fires. With IRF_NO_WAIT the callback only signals that data has arrived, so you have to issue the read again to actually retrieve it, and if you count the buffer length on that pending path you will end up over-counting. The end of the stream, by the way, is reported as TRUE with dwBufferLength equal to zero.
So rather than looping or sleeping, I would wait on an event that the callback sets when it receives INTERNET_STATUS_REQUEST_COMPLETE, and then re-issue the read. In practice the loop looks like this:
// Session in async mode, callback registered:
// hInet = InternetOpen(..., INTERNET_FLAG_ASYNC);
// InternetSetStatusCallback(hInet, Cb);
// Callback sets an event on INTERNET_STATUS_REQUEST_COMPLETE:
// case INTERNET_STATUS_REQUEST_COMPLETE:
// g_cbError = ((INTERNET_ASYNC_RESULT*)info)->dwError;
// SetEvent(g_hComplete);
char buf[2048];
INTERNET_BUFFERSA ib;
for (;;)
{
ZeroMemory(&ib, sizeof(ib));
ib.dwStructSize = sizeof(ib);
ib.lpvBuffer = buf;
ib.dwBufferLength = sizeof(buf);
if (InternetReadFileExA(hRequest, &ib, IRF_NO_WAIT, context))
{
if (ib.dwBufferLength == 0)
break; // end of stream
// ... consume ib.dwBufferLength bytes ...
}
else if (GetLastError() == ERROR_IO_PENDING)
{
// Data is not ready yet. Do not spin, do not Sleep.
WaitForSingleObject(g_hComplete, INFINITE); // signaled by the callback
if (g_cbError != ERROR_SUCCESS)
break; // handle the error
continue; // re-issue the read to fetch the data
}
else
{
break; // real error, handle GetLastError()
}
}
If a UI thread is involved, you would normally drive the read and the re-read entirely from the callback instead of blocking on WaitForSingleObject, so that the thread stays responsive. This is also the approach the official WinINet asynchronous sample takes, where it waits on an event and continues the next read from inside the callback, without any Sleep at all, and you can find it at Windows-classic-samples.
Please let me know if it would help to adapt this directly to your current code. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.
Thank you.