Windows: Refactor COM port read error handling#238
Open
Jason2866 wants to merge 2 commits intoserialport:mainfrom
Open
Windows: Refactor COM port read error handling#238Jason2866 wants to merge 2 commits intoserialport:mainfrom
Jason2866 wants to merge 2 commits intoserialport:mainfrom
Conversation
Removed error handling for GetOverlappedResult and added a note about ov->hEvent.
Updated error handling and comments in WriteIOCompletion and WriteThread functions to clarify usage of bytesTransferred and GetOverlappedResult.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The overlapped's hEvent holds a ReadBaton pointer (not a Windows event handle), so GetOverlappedResult fails with ERROR_INVALID_HANDLE on drivers that inspect hEvent (e.g. usbser.sys used by ESP32 native USB).
fix: serialport/node-serialport#3121 and serialport/node-serialport#3086
Why
GetOverlappedResultFails withERROR_INVALID_HANDLEThe internal implementation of
GetOverlappedResultworks by callingWaitForSingleObjectonpo->hEvent(if it is non-NULL), or on the file handle itself ifhEventis NULL.If
bWaitisTRUE,GetOverlappedResultdetermines whether the pending operation has been completed by waiting for the event object to be in the signaled state. IfhEventisNULL, the system uses the state of thehFilehandle instead.The problem occurs with certain third-party Windows COM drivers that inspect or manipulate the
hEventfield of theOVERLAPPEDstructure. When using IOCP, thehEventfield is oftenNULLor contains a value that is not a valid event handle (e.g., it may be used by the driver internally). When an asynchronous I/O request completes, the device driver checks to see whetherhEventisNULL. IfhEventis notNULL, the driver signals the event by callingSetEvent. Drivers that deviate from this contract and store invalid values inhEventcauseGetOverlappedResultto callWaitForSingleObjecton an invalid handle, resulting inERROR_INVALID_HANDLE.The Fix
When using IOCP, completions and failures should always be handled from the completion port worker thread.
GetOverlappedResultis generally used with non-completion-port overlapped I/O.In the IOCP completion callback (
WriteIOCompletion), thebytesTransferredvalue is delivered directly as a parameter by the Windows kernel — it is the same value that would have been read fromOVERLAPPED::InternalHighbyGetOverlappedResult. By removing theGetOverlappedResultcall and usingbytesTransferreddirectly from the callback parameter, the fix:hEvententirely, sidestepping theERROR_INVALID_HANDLEfailure on buggy/third-party drivers.bytesTransferred— no additional call toGetOverlappedResultis needed or recommended.errorCodeoverwrite bug, whereGetLastError()was called afterGetOverlappedResultcould have modified the last-error state.The Root Cause
The design intentionally stores a
WriteBaton*pointer inov->hEvent:This is explicitly allowed by MSDN for
WriteFileEx/ReadFileEx— the documentation states thathEventis not used by the system for APC-based I/O and is reserved for user data. The problem arose because the old code then calledGetOverlappedResulton this sameOVERLAPPED, whosehEventholds a baton pointer — not a valid Windows event handle.Certain drivers (notably
usbser.sys, used by ESP32 native USB, and drivers for CP210x/CH340-based USB serial chips) internally callWaitForSingleObject(ov->hEvent, ...)or otherwise validatehEventwhen servicingGetOverlappedResult. Since the value is a baton pointer (not a validHANDLE),WaitForSingleObjectfails immediately withERROR_INVALID_HANDLE.Why the Fix Is Correct
The new
WriteIOCompletionusesbytesTransferreddirectly from the APC callback parameter:This is correct for three reasons:
bytesTransferredin the APC callback is authoritative. The Windows kernel populates this value directly from the I/O completion — it is the same value thatGetOverlappedResultwould have read fromOVERLAPPED::InternalHigh. No information is lost.MSDN explicitly forbids
GetOverlappedResulthere. The comment in the code correctly cites MSDN: "Do not use GetOverlappedResult for I/O operations that use ReadFileEx or WriteFileEx completion routines."The fix eliminates all interaction with
hEventduring the write completion path, making the code immune to any driver-side inspection of that field.Consistency with
ReadIOCompletionThe same fix is applied to
ReadIOCompletion(same comment, same pattern). Notably,ReadIOCompletionstill usesGetOverlappedResultin its fallbackReadFilepath — but it correctly creates a real event handle there first:This is safe because
hEventis a properHANDLEat that point. The two code paths are now consistent and correct.