windows socket multithreading I/O Completion ports - windows

I wrote a windows socket server with I/O Completion port to handle heavy data transmission. It works smoothly when there is only one client connected. when more than one client connect, other threads just seem blocked while only one thread works fine.
I changed my design to use select() with each thread for each connection, the problem seems still the same. By the way, the same design works fine in OSX.
However, when I run multiple instances of my process, a process serves each connection, it works great.
Anyone kind enough to enlighten me? I still prefer multithread design. Core code as following:
other part of code basically comes from: http://www.codeproject.com/Articles/13382/A-simple-application-using-I-O-Completion-Ports-an
//Worker thread will service IOCP requests
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int nThreadNo = (int)lpParam;
void *lpContext = NULL;
OVERLAPPED *pOverlapped = NULL;
CClientContext *pClientContext = NULL;
DWORD dwBytesTransfered = 0;
int nBytesRecv = 0;
int nBytesSent = 0;
DWORD dwBytes = 0, dwFlags = 0;
//Worker thread will be around to process requests, until a Shutdown event is not Signaled.
while (WAIT_OBJECT_0 != WaitForSingleObject(g_hShutdownEvent, 0))
{
BOOL bReturn = GetQueuedCompletionStatus(
g_hIOCompletionPort,
&dwBytesTransfered,
(LPDWORD)&lpContext,
&pOverlapped,
INFINITE);
//WriteToConsole("\nThread %d: GetQueuedCompletionStatus.", nThreadNo);
if (NULL == lpContext)
{
//We are shutting down
break;
}
//Get the client context
pClientContext = (CClientContext *)lpContext;
if ((FALSE == bReturn) || ((TRUE == bReturn) && (0 == dwBytesTransfered)))
{
//Client connection gone, remove it.
RemoveFromClientListAndFreeMemory(pClientContext);
continue;
}
WSABUF *p_wbuf = pClientContext->GetWSABUFPtr();
OVERLAPPED *p_ol = pClientContext->GetOVERLAPPEDPtr();
switch (pClientContext->GetOpCode())
{
case OP_READ:
pClientContext->SetTransLen(dwBytesTransfered);
if(!pClientContext->IsComplete())
{
pClientContext->SetOpCode(OP_READ);
dwFlags = 0;
//Overlapped send
nBytesSent = WSASend(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSASend continue bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesSent) && (WSA_IO_PENDING != WSAGetLastError()))
{
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
WriteToConsole("\nThread %d: WSASend failed.", nThreadNo);
}
}
else
{
WriteToConsole("\nsocket %d: WSASend complete.", pClientContext->GetSocket());
//clear cache
pClientContext->ResetWSABUF();
//for the next recv, must be triggered.
pClientContext->SetOpCode(OP_WRITE);
//Get the data.
nBytesRecv = WSARecv(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, &dwFlags, p_ol, NULL);
if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSARecv().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
break;
case OP_WRITE:
pClientContext->SetTransLen(dwBytesTransfered);
if(pClientContext->IsComplete())
{
if(!pClientContext->ProcessCmd())
{
WriteToConsole("\nThread %d: ProcessCmd failed.", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
WriteToConsole("\nThread %d: receive completed.", nThreadNo);
//Send the message back to the client.
pClientContext->SetOpCode(OP_READ);
dwFlags = 0;
//Overlapped send
nBytesSent = WSASend(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSASend bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesSent) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSASend().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
else //continue receiving
{
pClientContext->SetOpCode(OP_WRITE);
//Get the data.
nBytesRecv = WSARecv(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, &dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSARecv continue bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSARecv().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
break;
default:
//We should never be reaching here, under normal circumstances.
break;
} // switch
} // while
return 0;
}
Updates: since my server app need to send large amount of data to multiple clients, it works fine maybe for some minutes, but somehow for no reason, some threads just block without responding. Does it have anything to do with the size of data?

Related

WinAPI. DeviceIoControl - Check overlapped request result

I've got kernel mode driver which handles user-mode requests asynchronously. Maximum number of requests in the queue, lats say, 32. All the following requests are completed with STATUS_INSUFFICIENT_RESOURCE status. I need to check in user-mode app if the requests was completed with this status. That's my user-mode app code:
HANDLE hEvents[40] = { 0 };
OVERLAPPED ovls[40] = { 0 };
int index = 0;
while (true)
{
hEvents[index] = CreateEvent(NULL, FALSE, FALSE, NULL);
ZeroMemory(&ovls[index], sizeof(OVERLAPPED));
ovls[index].hEvent = hEvents[index];
BOOL res = DeviceIoControl(hDevice, SEND_REQUEST_CTL, nullptr, 0,
nullptr, 0, &dwBytesRet, &ovls[index]);
++index;
if (res == FALSE)
{
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING)
{
WaitForMultipleObjects(index, hEvents, TRUE, INFINITE);
for (int i = 0; i < index; ++i)
CloseHandle(hEvents[i]);
}
}
}
I have array of hEvents and array of OVERLAPPED structures, because I need to wait for requests completion. So I the idea is that when driver returns STATUS_INSUFFICIENT_RESOURCE I just waiting for completion of all the IRPs that were queued to driver.
The problem is in that even when driver calls
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCE;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, 0);
GetLastError() from user-mode app returns ERROR_IO_PENDING so I can't handle STATUS_INSUFFICIENT_BUFFER driver error.
So my question is how can I check in user-mode app, that IRP was completed with STATUS_INSUFFICIENT_RESOURCE status?

select with other objects than sockets on windows

I'm facing an issue doing a select() call waiting on a socket + pipe.
I know there are already some topics on that but I have read lots of things and their opposite and I can't figure out what is the best solution for my problem.
The best for me would be to use WaitForMultipleObjects() listening on these two objects but when I try to call it only on the WSAEvent object, it fails and last error catch is code 6 (Invalid Handle).
WSAEVENT sockEvent = WSACreateEvent();
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
HANDLE *pHandles = &sockEvent;
DWORD dwEvent = WaitForMultipleObjects(1, pHandles, FALSE, amqp_time_ms_until(deadline));
switch (dwEvent)
{
// ghEvents[0] was signaled
case WAIT_OBJECT_0 + 0:
// TODO: Perform tasks required by this event
return AMQP_STATUS_OK;
// ghEvents[1] was signaled
case WAIT_OBJECT_0 + 1:
// TODO: Perform tasks required by this event
return AMQP_STATUS_POLL_EXTERNAL_WAKE;
case WAIT_TIMEOUT:
return AMQP_STATUS_TIMEOUT;
// Return value is invalid.
default:
return AMQP_STATUS_SOCKET_ERROR;
}
So WaitForMultipleObjects doesn't seems to Work with WinSocks events, however I have already seen some examples on the net working with it.
And the of WSACreateEvent documentation (https://msdn.microsoft.com/en-us/library/windows/desktop/ms741561%28v=vs.85%29.aspx) says this :
Windows Sockets 2 event objects are system objects in Windows
environments. Therefore, if a Windows application wants to use an
auto-reset event rather than a manual-reset event, the application can
call the CreateEvent function directly.
This doesn't mean that WSAEvent are based on regular windows events ? If it's the case why it doesn't work with WaitForMultipleObjects ? The doc says it can handle regular events.
Thanks for helping.
This is your problem:
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
You're overwriting the event handle! (As documented, the return value for WSAEventSelect is either 0 or SOCKET_ERROR. It is not a new event handle.)
Try something like
if (WSAEventSelect(fd, sockEvent, FD_WRITE) != 0) return SOCKET_ERROR;
Looking at the declaration of WSAEVENT revealed that WSAEVENT is simply an alias for HANDLE. This explains the note of the WSACreateEvent documentation you added to your post. So WSACreateEvent simply creates a manual reset event by calling CreateEvent(..., TRUE, FALSE, ...);.
Therefore an event returned by WSACreateEvent has to work along with WaitForMultipleObjects(..).
According to the code you've posted I cannot see any reason why WaitForMultipleObjects(..) should return "invalid handle" when supplied with an event returned by WSACreateEvent...
It may be though that pipes do not work with WaitForMultipleObjects(..). I remember having problems with that a long time ago but I cannot remember the details right now. But maybe it is another place to start digging...
Here is the code of my little test application which creates two threads (one event thread signalling a normal event and a simple TCP/IP server sending data). In the main loop a connection to the server is established and signalled events are processed.
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#pragma comment(lib, "Ws2_32.lib");
#define SERVER_PORT 5000
HANDLE hSomeEvent;
HANDLE hSocketEvent;
DWORD WINAPI eventThread(LPVOID pData)
{
while (1)
{
SleepEx(2250, FALSE);
SetEvent(hSomeEvent);
}
return (0);
}
DWORD WINAPI serverThread(LPVOID pData)
{
SOCKET listener;
struct sockaddr_in sockaddr;
int size;
SOCKET client;
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(SERVER_PORT);
if (bind(listener, (struct sockaddr *)&sockaddr , sizeof(sockaddr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d" , WSAGetLastError());
}
listen(listener, 1);
while (listener)
{
size = sizeof(struct sockaddr_in);
client = accept(listener, (struct sockaddr *)&sockaddr, &size);
printf("client connected\n");
while (client != INVALID_SOCKET)
{
SleepEx(5000, FALSE);
if (send(client, "hello\0", 6, 0) != 6)
{
closesocket(client);
shutdown(client, 2);
client = INVALID_SOCKET;
}
}
SetEvent(hSomeEvent);
}
return (0);
}
int main()
{
WSADATA wsaData;
HANDLE events[2];
DWORD result;
SOCKET s;
struct hostent *hp;
struct sockaddr_in sockaddr;
int len;
char buff[1024 * 16];
HANDLE *evtPtr;
WSAStartup(MAKEWORD(2, 2), &wsaData);
hSocketEvent = WSACreateEvent();
//hSocketEvent = CreateEvent(NULL, FALSE, FALSE, "socket_event");
hSomeEvent = CreateEvent(NULL, FALSE, FALSE, "some_event");
CreateThread(NULL, 0, eventThread, NULL, 0, &result);
CreateThread(NULL, 0, serverThread, NULL, 0, &result);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
hp = gethostbyname("127.0.0.1");
sockaddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(SERVER_PORT);
if (connect(s, (struct sockaddr*)&sockaddr, sizeof(sockaddr)))
{
closesocket(s);
printf("Could not connect socket : %d" , WSAGetLastError());
}
WSAEventSelect(s, hSocketEvent, FD_READ);
do
{
//events[0] = hSocketEvent;
//events[1] = hSomeEvent;
//result = WaitForMultipleObjects(2, events, FALSE, 1000);
evtPtr = &hSocketEvent;
result = WaitForMultipleObjects(1, evtPtr, FALSE, 1000);
switch (result)
{
case WAIT_OBJECT_0 + 0:
printf("hSocketEvent is signalled!\n");
len = recv(s, buff, sizeof(buff), 0);
printf(" %d bytes received\n", len);
WSAResetEvent(hSocketEvent);
break;
case WAIT_OBJECT_0 + 1:
printf("hSomeEvent is signalled!\n");
break;
case WAIT_TIMEOUT:
printf("timeout\n");
break;
default:
printf("error = %d\n", GetLastError());
break;
}
}
while (1);
printf("\n\nend.");
getch();
return (0);
}
Note that if you use WSACreateEvent you have to manually reset the event after readinng the data (otherwise WaitForMultipleObjects(..) will go nuts).

send and receive via Serial port Windows

I have a application running on windows, which will send data over serial port.
Here is the code:
m_hCommPort= ::CreateFile(L"\\\\.\\COM3",
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING,0,0);
if(m_hCommPort == INVALID_HANDLE_VALUE)
{
printf("COM error: %d\n", GetLastError());
}
config.DCBlength = sizeof(config);
if((GetCommState(m_hCommPort, &config) == 0))
{
CloseHandle(m_hCommPort);
printf("Get configuration port has a problem.\n");
return FALSE;
}
config.BaudRate = 9600;
config.StopBits = ONESTOPBIT;
config.Parity = PARITY_NONE;
config.ByteSize = DATABITS_8;
config.fDtrControl = 0;
config.fRtsControl = 0;
if (!SetCommState(m_hCommPort, &config))
{
CloseHandle(m_hCommPort);
printf( "Failed to Set Comm State Reason: %d\n",GetLastError());
return E_FAIL;
}
Here is the code for Send only (Working) (continuously sending )
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
After this I have added code for Receive data too, then send is NOT WORKING. I mean not able to send data over UART. WriteFile() seems not executed, its stuck.
Here I have added a thread to receive data, is thread causing the problem ? or do I need to do something else ?
void ReceiverThread(void *param)
{
DWORD dwRead=0;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
printf("Error creating overlapped event; abort.\n");
while(1)
{
if (!ReadFile(m_hCommPort, &Byte, 1, &dwRead, &osReader)) {
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
printf("Error in communications; report it.\n");
else
fWaitingOnRead = TRUE;
}
else {
rxData[rxHeadIndex++]= Byte;
rxHeadIndex = (rxHeadIndex) & QUEUE_MASK;
}
}
}
SetCommMask (m_hCommPort, EV_RXCHAR/ | EV_ERR); //receive character event
_beginthread(ReceiverThread,0,NULL);
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
Thanks in advance.
Ashok
I've encountered a similar problem a while ago.
I found out that WriteFile(..) blocks if a ReadFile(..) is currently in progress for a serial port. So that's a problem if ReadFile(..) blocks if there is no data to be read.
I've solved the problem by checking if there is data available within the serial buffer to be read by using the function ClearCommError(..). This ensures that ReadFile(..) can read something immediately and does not unnecessarily block the device. Try changing your ReceiverThread into something like this:
void ReceiverThread(void *param)
{
DWORD dwRead=0;
COMSTAT comStat;
char buffer[1024];
while (m_hCommPort != INVALID_HANDLE_VALUE)
{
if ((ClearCommError(m_hCommPort, NULL, &comStat) != FALSE) && (comStat.cbInQue > 0))
{
/* todo: ensure buffer is big enough to contain comStat.cbInQue bytes! */
if (ReadFile(m_hCommPort, buffer, comStat.cbInQue, &dwRead, NULL) != FALSE)
{
/* do something with data in buffer */
}
}
/* avoid busy-wait */
if (comStat.cbInQue == 0)
{
SleepEx(1, FALSE);
}
}
}
This way ReadFile(..) is only called if data is available and meanwhile WriteFile(..) can send data without being blocked.
Unfortunately I've not been able to make ClearCommError(..) blocking so I used the SleepEx(1, FALSE); work-around to avoid a busy-wait and therefore prefenting the ReceiverThread to eat up the CPU.
config.fDtrControl = 0;
config.fRtsControl = 0;
These settings turn the DTR and RTS handshake lines off. Most serial devices pay attention to these signals. They won't send anything when your DTR signal is off, assuming that the machine is not powered up. And won't send anything when your RTS signal is off, assuming that the machine is not ready to receive any data.
So what you observed is entirely normal.
Since the device appears to be "normal" and does pay attention to the handshake lines, you'll want to configure the DCB to let the device driver automatically control these signals. Fix:
config.fDtrControl = DTR_CONTROL_ENABLE;
config.fRtsControl = RTS_CONTROL_HANDSHAKE;
Also the default for terminal emulators like Putty and HyperTerminal. Use such a program first to ensure that the wiring and device are functional. If you can't get any device data to show up in such a program then it won't work with your program either. If this all checks out then also set the fDsrSensitivity, fOutxCtsFlow and fOutxDsrFlow properties to TRUE so that you will, in turn, pay attention to the handshake signals of the device.

select() for blocking socket in windows

void CSocket::WaitForConnetion()
{
CSocket* pSocket = NULL;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = m_hSocket;
timeval timeout;
ZeroMemory(&timeout, sizeof(timeout));
timeout.tv_sec = 10;
while(!g_bQuit)
{
int iSelect = select(NULL, &readfds, NULL, NULL, &timeout);
if (iSelect == SOCKET_ERROR)
{
// error
break;
}
else
{
if (iSelect == 0)
{
// timeout
}
else
{
assert(iSelect == 1);
SOCKET new_sock = accept(m_hSocket, NULL, NULL);
if (new_sock != INVALID_SOCKET)
{
pSocket = new CSocketEx(new_sock);
// create a new thread to process the connection request with pSocket
}
}
continue;
}
}
Hi,all
CSocket is a socket wrapper.
Using blocking socket in my programm,calling accept function will be blocked when i want to exit thoroughly, so i have to terminate the thread calling accept.I want to use select function as above.But select often goes with non-blocking socket, are there any problems or concerns using blocking socket under such environment above?
With EJP's help.I modified the code below.Is there any error? And as you said if i use the way by closing socket handle,i must pass the socket handle to the other thread then close it,is it safty to operator one sokcet in different threads?
void CSocket::WaitForConnetion()
{
CSocket* pSocket = NULL;
timeval timeout;
ZeroMemory(&timeout, sizeof(timeout));
timeout.tv_sec = 10;
while(!g_bQuit)
{
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = m_hSocket;
int iSelect = select(NULL, &readfds, NULL, NULL, &timeout);
if (iSelect == SOCKET_ERROR)
{
// error
break;
}
else
{
if (iSelect == 0)
{
// timeout
}
else
{
assert(iSelect == 1);
SOCKET new_sock = accept(m_hSocket, NULL, NULL);
if (new_sock != INVALID_SOCKET)
{
pSocket = new CSocketEx(new_sock);
// create a new thread to process the connection request with pSocket
}
}
continue;
}
}

SerialPorts and WaitForMultipleObjects

I'm having some problems with serial ports in a cross-platform application (with Linux embedded and actual embedded targets), which also works on Windows to make development easier. This is about the Windows implementation.
The implementation of the serial protocol is, therefore, targetted at a mixture of OS- and non-OS systems and I won't touch the implementation itself. I'd like to make it compatible with the existing implementation. If that fails within reasonable time, I'll just make a separate thread for serial reading.
OK, basically the implementation opens the serial port, registers the file descriptor in our IO system (which uses epoll on Linux and WaitForMultipleObjects on Windows) and then, basically, just waits for all handles and does whatever required. So we want to read from the serial port when the handle is signaled for reading. Unfortunately on Windows, you can't specify if you're waiting for read or write, so I thought I'd use the following solution:
CreateFile with FILE_FLAG_OVERLAPPED
SetCommMask with EV_RXCHAR
Create an OVERLAPPED structure with a manual reset event
Call WaitCommEvent with said OVERLAPPED structure, which usually returns ERROR_IO_PENDING
That's the basic setup. I register the event handle instead of the file handle to wait on. When the handle is signalled, I do the following:
ReadFile
If successful, ResetEvent and call WaitCommEvent again
It seems, however, that if you specify FILE_FLAG_OVERLAPPED, you must use overlapped IO also for reading and writing. So I thought that whenever ReadFile or WriteFile return ERROR_IO_PENDING, I'll just wait for the IO with WaitForSingleObject and GetOverlappedResult. It seems that I don't get into that though. It seems to work basically, but sometimes it crashes on one of the ResetEvent calls, as if the overlapped was still active (though I guess it still shouldn't crash).
So, the actual question. Can this be done as I want it? Is there a problem with the approach in general, or should it work? Or is using yet another thread the only good solution? The communication is already in a separate thread, so it would be at least three threads then.
I'll try to post as much code as needed, though it is reduced from the actual code which contains a lot of things not directly related to serial reading.
SerialPort::SerialPort(const std::string &filename)
{
fd = INVALID_HANDLE_VALUE;
m_ov = new OVERLAPPED(); // Pointer because header shouldn't include Windows.h.
memset(m_ov, 0, sizeof(OVERLAPPED));
m_waitHandle = m_ov->hEvent = CreateEvent(0, true, 0, 0);
}
SerialPort::~SerialPort(void)
{
Close();
CloseHandle(m_ov->hEvent);
delete m_ov;
}
The constructor is called in a separate thread, which later calls Open:
bool SerialPort::Open(void)
{
if (fd != INVALID_HANDLE_VALUE)
return true;
fd = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (fd != INVALID_HANDLE_VALUE) {
DCB dcb;
ZeroMemory(&dcb, sizeof(DCB));
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = TimeOut();
timeouts.ReadTotalTimeoutConstant = TimeOut();
timeouts.ReadTotalTimeoutMultiplier = TimeOut() / 5;
if (timeouts.ReadTotalTimeoutMultiplier == 0) {
timeouts.ReadTotalTimeoutMultiplier = 1;
}
if (!SetCommTimeouts(fd, &timeouts)) {
DebugBreak();
}
SetCommMask(fd, EV_RXCHAR);
InitWait();
return true;
}
return false;
}
void SerialPort::InitWait()
{
if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
return; // Still signaled
}
DWORD dwEventMask;
if (!WaitCommEvent(fd, &dwEventMask, m_ov)) {
// For testing, I have some prints here for the different cases.
}
}
Via a rather long chain, the thread then calls WaitForMultipleObjects on m_waitHandle, which is the same as the hEvent member of the OVERLAPPED structure. This is done in a loop, and there are several other handles in the list, that's why this is different from the typical solution where you have a thread exclusively reading from the serial port. I have, basically, no control about the loop, that's why I try to do the WaitCommEvent (within InitWait) at just the right time.
When the handle is signaled, the ReadData method is called by the thread:
int SerialPort::ReadData(void *buffer, int size)
{
if (fd != INVALID_HANDLE_VALUE) {
// Timeouts are reset here to MAXDWORD/0/0, not sure if necessary.
DWORD dwBytesRead;
OVERLAPPED ovRead = {0};
ovRead.hEvent = CreateEvent(0, true, 0, 0);
if (ReadFile(fd, buffer, size, &dwBytesRead, &ovRead)) {
if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
// Only reset if signaled, because we might get here because of a timer.
ResetEvent(m_waitHandle);
InitWait();
}
CloseHandle(ovRead.hEvent);
return dwBytesRead;
} else {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ovRead.hEvent, INFINITE);
GetOverlappedResult(fd, &ovRead, &dwBytesRead, true);
InitWait();
CloseHandle(ovRead.hEvent);
return dwBytesRead;
}
}
InitWait();
CloseHandle(ovRead.hEvent);
return -1;
} else {
return 0;
}
}
The write is done as follows, without syncing:
int SerialPort::WriteData(const void *buffer, int size)
{
if (fd != INVALID_HANDLE_VALUE) {
DWORD dwBytesWritten;
OVERLAPPED ovWrite = {0};
ovWrite.hEvent = CreateEvent(0, true, 0, 0);
if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite)) {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ovWrite.hEvent, INFINITE);
GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, true);
CloseHandle(ovWrite.hEvent);
return dwBytesWritten;
} else {
CloseHandle(ovWrite.hEvent);
return -1;
}
}
CloseHandle(ovWrite.hEvent);
}
return 0;
}
It seems that it does work now. There are no crashes anymore, at least I can't reproduce them. So as it works now, I'm just asking if what I do is sane, or if I should do things differently.
Offhand, I don't see any errors in the code you have shown, but I would like to suggest alternative code to clean up your error handling in ReadData() and WriteData() in general:
int SerialPort::ReadData(void *buffer, int size)
{
if (fd == INVALID_HANDLE_VALUE)
return 0;
OVERLAPPED ovRead = {0};
ovRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ovRead.hEvent)
return -1;
DWORD dwBytesRead;
if (!ReadFile(fd, buffer, size, &dwBytesRead, &ovRead))
{
if (GetLastError() != ERROR_IO_PENDING)
{
CloseHandle(ovRead.hEvent);
return -1;
}
if (!GetOverlappedResult(fd, &ovRead, &dwBytesRead, TRUE))
{
CloseHandle(ovRead.hEvent);
return -1;
}
}
if (WaitForSingleObject(m_waitHandle, 0) == WAIT_OBJECT_0)
{
ResetEvent(m_waitHandle);
InitWait();
}
CloseHandle(ovRead.hEvent);
return dwBytesRead;
}
int SerialPort::WriteData(const void *buffer, int size)
{
if (fd == INVALID_HANDLE_VALUE)
return 0;
OVERLAPPED ovWrite = {0};
ovWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ovWrite.hEvent)
return -1;
DWORD dwBytesWritten;
if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite))
{
if (GetLastError() != ERROR_IO_PENDING)
{
CloseHandle(ovWrite.hEvent);
return -1;
}
if (!GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, TRUE))
{
CloseHandle(ovWrite.hEvent);
return -1;
}
}
CloseHandle(ovWrite.hEvent);
return dwBytesWritten;
}

Resources