jcurl / RJCP.DLL.SerialPortStream

SerialPortStream is an independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. Default branch is 2.x and now has support for Mono with help of a C library.
Microsoft Public License
614 stars 196 forks source link

System.Runtime.InteropServices.COMException: 'The semaphore timeout period has expired. (0x80070079)' #146

Closed tyouard closed 3 months ago

tyouard commented 3 months ago

Hi Jason,

I am having an intermittent issue where after opening the port, the first write() call on the serial port causes an exception.

System setup:

I am using the OpenDirect() method since this is a virtual COM port where the port settings not make sense. This succeeds without any issue. Then on first write to the port the following sometimes occurs:

12:01:20.916 +12:00 [DBG] PacketSendReceive sending packet len=18 data='[0] info version\r\n'
12:01:20.941 +12:00 [DBG] COM4: SerialThread: DoWriteEvent: WriteFile(2888, 2969386590832, 18, ...) == False
12:01:21.432 +12:00 [DBG] COM4: SerialThread: ProcessWaitCommEvent: EV_TXEMPTY
12:01:21.435 +12:00 [DBG] COM4: CommEvent: EV_TXEMPTY
12:01:21.439 +12:00 [ERR] COM4: SerialThread: Overlapped WriteFile() error 121 bytes 0
Exception thrown: 'System.Runtime.InteropServices.COMException' in System.Private.CoreLib.dll
The semaphore timeout period has expired. (0x80070079)

Once it happens it will happen every-time until I disconnect the USB cable then reconnect. After that it will start working again. The call stack is: image

I'm thinking there is an issue in either the windows usbser driver, or the USBD_VCOM implementation on the microcontroller, but I thought I would record this here just in case you have any ideas. My next step is to capture and investigate the USB traffic, although this is not something I am knowledgeable at. Also, it looks like this issue might be related? https://github.com/jcurl/RJCP.DLL.SerialPortStream/issues/123

tyouard commented 3 months ago

Update: I can 'fix' this issue if I use Open() instead of OpenDirect(). I was using OpenDirect() because I thought the settings such as baud rate, stop bits, etc do not make any sense for a virtual COM port, and because I was trying to workaround another issue which I'll describe below. It seems the port settings needed to be set at least once for each power cycle on the remote device in order for the virtual com port to work.

The other issue I have is occasionally the Open() call will block and freeze my app. It is getting stuck on function GetCommState() or SetCommState() call. It is an occasional issue which can only be fixed by unplugging and re-plugging in the USB cable.

jcurl commented 3 months ago

The reason for the method OpenDirect is now lost due to Microsoft CodePlex being shutdown (when development was first on Subversion). From what I've found in my local notes, it was due to a user having problems with a CDC device not supporting SetCommState:

Create a new method that allows to open a port without actually setting any properties with the public method OpenDirect(). This fixes a problem that the Internet user Netfusion had with USB CDC devices that don't actually have an RS232 on the other side, so that an Open() without a SetCommState() will work (and which resulted in timeouts, delays and instability).

You could try using OpenDirect, but still set the properties for the baudrate, databits, handshake, etc. immediately after, and see if this improves stability.

Please note, I don't have any such devices that cause these problems, so I've never had the opportunity to debug locally.

tyouard commented 3 months ago

Hi Jason. Thanks for the information. I have a workaround for the issue for open() call blocking and getting stuck. I used wireshark to capture and compare USB traffic for working and not working open() call.

When open() is working I can see there is no serial port data sent until after open() is completed configuring the serial port. The wireshark capture below shows the first serial port data packet (highlighted yellow) is happening after serial port has been configured. image

In the failing case where it gets stuck in SetCommState() there is serial port data being sent from the device to the host before the port has finished being configured. For some reason this is causing the serial port driver in windows to get confused and leading to blocking SetCommState() function. The wireshark capture shows the first serial port data packet (highlighted yellow) is happening during configuration of the port (while open() call is still executing). image

The workaround I used is to ensure the remote device does not send any serial port data to the host until after open() is completed.

jcurl commented 3 months ago

@tyouard thank you very much for the detailed analysis! I think you results are such an important topic, it is worthwhile adding to a wiki or other documentation. Can I use your analysis?

tyouard commented 3 months ago

@jcurl Yes, you can use the analysis

jcurl commented 2 months ago

I tried reproducing using a TTL PL2303TW device from my RPi4B, unfortunately I was not able to reproduce. I'll see what other devices I have.