Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the ProactorEventLoop APIs on Windows #91

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

hugh-manning
Copy link

Potential solution to #3, based on the fact that we can access the Windows ReadFile/WriteFile API through the ProactorEventLoop by calling its sock_recv and sock_sendall methods, passing a file-ish object as the first argument instead of a socket object.

Using sock_recv and sock_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 the sock_recv and sock_sendall methods, which isn't much better. The documentation for sock_recv specifically states that it requires a non-blocking socket, even though the underlying code will accept any object which implements a fileno() method.

Note that we still need to directly access the private _port_handle attribute of the Serial object. That access can be removed if PySerial implements the fileno() method for Win32 Serial 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 the ProactorEventLoop's await read/await write style of API, some adaptation is required:

  • This change starts co-routines to service the read and write operations. This minimises the changes required to the cross-platform code, but adds complexity to the Windows case. In particular: if the read or write coroutine were to raise an exception then the coroutine will be cleaned up, but the exception will not be easily handled, and the servicing co-routine would not be restarted until the next call to SerialTransport.write() or SerialTransport.resume_reading().
  • Calling 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 the ProactorEventLoop. I suspect, but haven't confirmed, that this would cause problems if there are multiple asynchronous reads happening at the same time.
  • The 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.

Potential solution to pyserial#3, based on the fact that we can access the
Windows ReadFile/WriteFile API through the ProactorEventLoop by calling
its sock_recv and sock_sendall methods, passing a file-ish object as the
first argument instead of a socket object.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant