Today I spent several hours hunting down a problem with opening a COM port. Basically the program opens a COM port, writes some bytes to it, reads an answer and closes it again. This determines whether the expected device is connected to the COM port or not.
If the answer is the expected one, so the device is connected and active, the detection loop exists (the COM port has been closed using CloseHandle).
Now the actual communication with the device starts. The first thing is does, is open the same COM port again. Which fails with the error code 5 (ERROR_ACCESS_DENIED). WTF?
Of course, I tried to debug the issue using the Delphi integrated debugger. While I stepped through the code, the error disappeared, only to come back when I just run the code without stepping through it. This was reproducible (I love reproducible errors.).
When something like this happens, you can be sure it is a timing issue. Either your program is multithreaded and the issue is one thread doing something while the other is doing something else that conflicts with the first thread (called race condition or deadlock, depending on the outcome). Or it’s not your own threads but somebody else’s.
In my case this StackOverflow question seems to be about the same issue and the accepted answer was mentioning the FTDI drivers I am using (the COM port is actually a USB serial adapter). These drivers apparently do not immediately close the port when the handle is closed so opening it again can fail. It turned out that a retry loop when opening the COM port solved the issue. Here is the code I ended up using:
procedure TCustomComPort.CreateHandle; const MAX_TRIES = 10; var Tries: integer; OK: boolean; begin Tries := 0; repeat Inc(Tries); FHandle := CreateFile( PChar('\\.\' + FPort), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); OK := (FHandle <> INVALID_HANDLE_VALUE); if not OK then Sleep(10 * Tries); // it wasn't enough to Sleep(10) here until OK or (Tries >= MAX_TRIES); if not OK then CallException(CError_OpenFailed, GetLastError); end;
Some of you might recognize part of this code from the ComPort library for Delphi and C++. (But probably not, because the original code is just three lines. 😉 )
Of course the real debugging was more complex than I described above because the program is multi threaded and the detection is done in one thread while the actual communication is done with the foreground thread writing to the port and the answers being processed by a background thread. So of course, at first I suspected an error in my code, actually found one, fixed it, just to run into the next problem. Finally it turned out to be the driver problem described here.