Use the ProactorEventLoop APIs on Windows #91
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.
Potential solution to #3, based on the fact that we can access the Windows
ReadFile/WriteFileAPI through theProactorEventLoopby calling itssock_recvandsock_sendallmethods, passing a file-ish object as the first argument instead of a socket object.Using
sock_recvandsock_sendallallows us to avoid directly accessing private attributes (as seen in m-labs/asyncserial). I'm not entirely convinced of this solution, because it still relies on undocumented private implementation details of thesock_recvandsock_sendallmethods, which isn't much better. The documentation forsock_recvspecifically states that it requires a non-blocking socket, even though the underlying code will accept any object which implements afileno()method.Note that we still need to directly access the private
_port_handleattribute of theSerialobject. That access can be removed if PySerial implements thefileno()method for Win32Serialobjects, which would also allow the adaptor class in this pull request to be removed.Given the difference between the
SelectorEventLoop's callback style of API and theProactorEventLoop'sawait read/await writestyle of API, some adaptation is required:SerialTransport.write()orSerialTransport.resume_reading().sock_recvwith a read timeout of 0 effectively results in a busy wait, so we need to set some timeout. Since we don't know how much data to read ahead of time, we need to do a blocking read of one byte, then a non-blocking read of_max_read_size - 1bytes to read any remaining data on the port. Despite the documented requirement for non-blocking sockets, this doesn't seem to cause problems, probably due to the use of overlapped I/O in theProactorEventLoop. I suspect, but haven't confirmed, that this would cause problems if there are multiple asynchronous reads happening at the same time.sock_sendallmethod provides no way of knowing how much data was written. We have to assume that all bytes in any given call are sent successfully. In the event that the writing co-routine is cancelled mid-write, we need to decide whether to put the written data back into the write buffer (risking partial retransmission) or to drop it (risking that some bytes aren't transmitted at all). This change uses the former approach, which works better for my use case, but I'm not sure what the best choice is for general use.