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
/WriteFile
API through theProactorEventLoop
by calling itssock_recv
andsock_sendall
methods, passing a file-ish object as the first argument instead of a socket object.Using
sock_recv
andsock_sendall
allows 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_recv
andsock_sendall
methods, which isn't much better. The documentation forsock_recv
specifically 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_handle
attribute of theSerial
object. That access can be removed if PySerial implements thefileno()
method for Win32Serial
objects, 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 write
style of API, some adaptation is required:SerialTransport.write()
orSerialTransport.resume_reading()
.sock_recv
with 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 - 1
bytes 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_sendall
method 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.