feat(win): Add non-blocking overlapped I/O implementation for Windows#19
Open
lrodorigo-versuni wants to merge 13 commits into
Open
feat(win): Add non-blocking overlapped I/O implementation for Windows#19lrodorigo-versuni wants to merge 13 commits into
lrodorigo-versuni wants to merge 13 commits into
Conversation
- Open port with FILE_FLAG_OVERLAPPED instead of FILE_ATTRIBUTE_NORMAL - Use OVERLAPPED structs with events for ReadFile/WriteFile - Handle ERROR_IO_PENDING + WaitForSingleObject for proper timeout - Implement waitReadable() using WaitCommEvent with overlapped I/O - Implement waitByteTimes() with calculated sleep - Use overlapped WaitCommEvent in waitForChange() - Cancel pending I/O on close() This allows concurrent read/write from different threads without blocking. Previously, a write during a pending read would block until the read timeout expired.
When -DSERIAL_CPP_WIN_ASYNC=ON is passed to CMake, the library will be built with the overlapped (async) Windows implementation that allows concurrent read/write from different threads. Default is OFF, preserving the original blocking behavior.
…PPED/stack pointers Key fixes: - Use local OVERLAPPED structs on the stack but ALWAYS wait for completion (GetOverlappedResult with bWait=TRUE) or cancel+wait before leaving scope. This ensures Windows never writes to freed memory. - Use CancelIoEx instead of CancelIo (targets specific overlapped op). - Make wait_comm_event_mask_ a class member since WaitCommEvent writes to it asynchronously after the call returns. - Use separate local event for waitForChange() to avoid conflicts. - Properly handle ERROR_OPERATION_ABORTED after cancellation. - Initialize events in constructor before open() to avoid use-before-init.
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.
Problem
The Linux/Unix implementation uses
select()/poll()under the hood, which naturally allows concurrent read and write operations from different threads. A reader thread waiting for data does not block a writer thread from sending data — they are fully independent.The Windows implementation, on the other hand, uses synchronous
ReadFile/WriteFile(opened withFILE_ATTRIBUTE_NORMALandNULLoverlapped parameter). This means read and write serialize on the same handle: if a thread is blocked inReadFilewaiting for incoming data, any other thread callingWriteFilewill be stuck until the read times out.This behavioral difference between Linux and Windows drove me absolutely insane. Code that worked perfectly on Linux — with a reader thread continuously polling for data while a writer thread sends commands — would completely deadlock on Windows. The writer would freeze for the entire duration of the read timeout, making real-time bidirectional communication impossible. I spent way too long debugging this before realizing it was a fundamental limitation of the synchronous Win32 serial API.
Solution
This PR adds an alternative Windows implementation using overlapped (asynchronous) I/O (
FILE_FLAG_OVERLAPPED), selectable via a CMake option:win.cc/win.hare untouched. Default isSERIAL_CPP_WIN_ASYNC=OFFbut can be turned ON to enable the new non-blocking behavior.WaitCommEventmask is a class member: Windows writes to thelpEvtMaskDWORD asynchronously afterWaitCommEventreturnswaitReadable()andwaitByteTimes()now work on Windows (previously they just threw "not implemented").New Test program
I also added a small test program (
examples/serial_cpp_async_test.cc) that demonstrates the issue and verifies the fix. It spawns a reader thread and a writer thread operating concurrently on the same serial port. With the old blocking implementation the writer would stall for the entire read timeout; withSERIAL_CPP_WIN_ASYNC=ONboth threads proceed independently, as expected.Usage
This makes the Windows behavior match Linux: read and write from separate threads operate independently without blocking each other.