Comport timeout with overlapped operation during high CPU usage

First of all, this question is not the "ordinary" how to use WaitCommEvent and such. Instead I need help to understand the timing and optimal/correct usage and understanding of the threads and how to "tell Windows to boost the comport driver" to report a possible event even when the CPU load is very high??

I have a problem with an application that is beeing used to load an external device with a firmware through a comport.

I have a separate thread divided from the user interface thread that writes X amount of bytes to the external machine and then waits for a single byte to be returned from the machine, as a kind of confirm byte.

The problem is that I sometimes get a timeout from the "WaitCommEvent" and/or the aftercomming WaitForMultipleObjects (if WaitCommEvent returns ERROR_IO_PENDING, the WaitFor... is called with the event for the overlaped structure).

Even dough I know that data has been received I get this timeout during high CPU load.

So if I stress the CPU by moving the application window rapidly over the screen and never stop the mouse drag for the timeout period I stated in the "WaitFor" command, I will receive a timeout even dough I know that there has been a byte sent from the external machine to the PC.
In other words, my problem only shows up when the CPU is under stress.

At the moment it seems like the comport driver does not flag the RX_CHAR if the CPU usage is very high on the system. The RX_CHAR flag is "lit up" directly when the CPU load is lowered, however, the "WaitFor" functions is still counting the time during the high CPU load, so it causes my code to report for timeout in comport communication during high load of the system if the high CPU load lasts longer then the timeout value supplied to the "WaitCommEvent/WaitFor..." function.

Either my comprt handling design is bad or I have to do something to make the comport driver to run in "TimeCritical" priority and make sure it reports the RX_CHAR so that my overlapped.hEvent is triggered before the timeout period.

I have tried some things with the priority of the thread that calls WaitCommEvent/WaitForMultipleObjects, setting it to TimeCritical without any possitive change in the timeout results.

Below is my code (Delphi) that is used to wait for the confirmation byte from my external equipment connected to the PC.

I can be doing something wrong in this code that only causes trouble during high CPU load?

---

function TELSRS232.ReceiveByte(Timeout : DWord; out ReadByte : byte) : boolean;
var OLBlock: TOverlapped;
    Mask: DWORD;
    Success,IOTimeout : Boolean;
    BytesTrans,InpCnt,LastErr : Cardinal;
    Signaled, EventHandleCount: Integer;
    EventHandles: array[0..2] of THandle;
    AIOSignaled,ATerminateSignaled,AAbortSignaled : boolean;
begin
 result := false;
 ReadByte := $FF;
 if not Terminated then begin
  FillChar(OLBlock, SizeOf(TOverlapped), 0);
  OLBlock.hEvent := FIOEvent;
  EventHandles[0] := FIOEvent;
  EventHandles[1] := FTerminateEvent; // Terminate thread event...
  EventHandles[2] := FFlashAbortEvent; // Abort Flash event...
  ResetEvent(FIOEvent);
  Signaled := 0;
  SetCommMask(FComPort.Handle,EV_RXCHAR);
  SetThreadPriorityBoost(self.Handle,true); // <- This is only a test, I'm not sure it helps anything at all
  Success := WaitCommEvent(FComPort.Handle,ComMask,@OLBlock);
  LastErr := GetLastError;
  if (Success) or (LastErr = ERROR_IO_PENDING) then begin  
   // If WaitCommEvent returned Success = TRUE, then the overlapped.hEvent should still remain high..
   // making the next call to the WaitForMultipleObjects catch the same event again and return direct?
   Signaled := WaitForMultipleObjects(1, @EventHandles, False, Timeout); // Wait for any of 3 events
   AIOSignaled := (WaitForSingleObject(FIOEvent,0) = WAIT_OBJECT_0); // Check event
   ATerminateSignaled := (WaitForSingleObject(FTerminateEvent,0) = WAIT_OBJECT_0); // Check event
   AAbortSignaled := (WaitForSingleObject(FFlashAbortEvent,0) = WAIT_OBJECT_0); // Check event
   if (Signaled = WAIT_OBJECT_0) then begin
    Success := (ComMask = EV_RXCHAR);
    SetCommMask(FComPort.Handle,0); // reset the event
    IOTimeout := false;
   end else begin
    if (Signaled = WAIT_TIMEOUT) then begin
     SetCommMask(FComPort.Handle, 0); // reset the event
     IOTimeout := true;
    end else IOTimeout := false;
   end;
  end else begin
   // Here we come if WaitCommEvent failed with anything else then ERROR_IO_PENDING
   WriteLogText(SysErrorMessage(LastErr));
   AIOSignaled := (WaitForSingleObject(FIOEvent,0) = WAIT_OBJECT_0);
   ATerminateSignaled := (WaitForSingleObject(FTerminateEvent,0) = WAIT_OBJECT_0);
   AAbortSignaled := (WaitForSingleObject(FFlashAbortEvent,0) = WAIT_OBJECT_0);
   IOTimeout := true;
  end;
  SetThreadPriorityBoost(Handle,false); // <- This is only a test, I'm not sure it helps anything at all
  if not IOTimeout then begin
   if Success then begin // There is a byte to read
    InpCnt := FComPort.InputCount;
    if (InpCnt > 0) then begin
     result := (FComPort.Read(ReadByte,1) >= 1);
     if result then inc(FReceivedCnt,1);
    end;
   end;
   if ATerminateSignaled then WriteLogText('TERMINATE EVENT TRIGGERED');
   if AAbortSignaled then WriteLogText('ABORT EVENT TRIGGERED');
  end else begin
   inc(FTimeoutCnt,1);
   WriteLogText('I/O TIMEOUT');
   if AIOSignaled then WriteLogText('I/O (RX or FRAME ERROR) TRIGGERED');
   if ATerminateSignaled then WriteLogText('TERMINATE EVENT TRIGGERED');
   if AAbortSignaled then WriteLogText('ABORT EVENT TRIGGERED');
  end;
 end;
end;

---
LVL 1
ProbieAsked:
Who is Participating?
 
GranModCommented:
PAQed with points refunded (500)

GranMod
Community Support Moderator
0
 
AlexFMCommented:
  Signaled := WaitForMultipleObjects(1, @EventHandles, False, Timeout); // Wait for any of 3 events
   AIOSignaled := (WaitForSingleObject(FIOEvent,0) = WAIT_OBJECT_0); // Check event
   ATerminateSignaled := (WaitForSingleObject(FTerminateEvent,0) = WAIT_OBJECT_0); // Check event
   AAbortSignaled := (WaitForSingleObject(FFlashAbortEvent,0) = WAIT_OBJECT_0); // Check event

I am not sure whether this can help, but why do you need additional calls to WaitForSingleObject? WaitForMultipleObjects returns  WAIT_OBJECT_0 is first event in array is signaled,  WAIT_OBJECT_0 + 1 if second event is signaled etc. You need to make array of events and wait all of them in one WaitForMultipleObjects call.
0
 
ProbieAuthor Commented:
True AlexFM, it's stupid.. and I see now that I feed WaitForMultipleObjects(1, @EventHandles, False, Timeout) with "1", telling it to only check the first of my three events. However none of these "bugs" affect the reason of this question.. and the problem is still the same even after changing this.
0
 
mxjijoCommented:

I'm not sure whether there is a simple solution for this.
I can imagine this can happen if the application threads have higher priorities than threads in the driver.
Therefore I think increasing your application thread priority can make things even worse !!

May be its a good idea to reduce your thread's priority while waiting.
That way you can make sure that the driver threads has a bit upper hand.
Also it will work normally on normal CPU loads.

The other option is to play smart, change wait timeout according to the current cpu usage.

0
 
ProbieAuthor Commented:
I found "a" solution to my own problem..

Instead of accepting a timeout when I get one and do a break, I catch the timeout and check the input buffer with "ClearCommError"
If "Comstat.cbInQue > 0", then I know that there has been a byte recieved and can catch it before the Overlapped.hEvent becomes set.

---

  SetCommMask(FComPort.Handle,EV_RXCHAR);
  Success := WaitCommEvent(FComPort.Handle,ComMask,@OLBlock);
  if not Success then begin
   LastErr := GetLastError;
   if (LastErr = ERROR_IO_PENDING) then begin
    Signaled := WaitForMultipleObjects(3,@EventHandles,False,Timeout);
    case Signaled of
     WAIT_OBJECT_0+0: begin
      AIOSignaled := true;
      Success := (ComMask = EV_RXCHAR);
     end;
     WAIT_OBJECT_0+1: ATerminateSignaled := true;
     WAIT_OBJECT_0+2: AAbortSignaled := true;
    else
     ClearCommError(FComPort.Handle,ComErrors,@ComStat);
     if (ComStat.cbInQue > 0) then begin
      Success := true;
     end else begin
      IOTimeout := true;
     end;
    end;
   end else begin
    WriteLogText(SysErrorMessage(LastErr));
   end;
  end else begin
   Success := (ComMask = EV_RXCHAR);
  end;
  SetCommMask(FComPort.Handle,0);

---
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.