Skip to content

Conversation

ceharris
Copy link
Contributor

@ceharris ceharris commented Apr 29, 2023

For a senior-level university network apps design course, my students were using the threaded version of WebSocketServer as part of their final project. While the project required their work to run successfully in containers on Linux, many of them use Windows for their development workstation. We discovered that serve_forever throws an error at startup on Windows due to the use of select.poll as the means to block while waiting for incoming socket connections. The documentation for poll indicates that it is not supported on all platforms, and apparently Windows is one such platform.

I patched WebSocketServer to instead use selectors.DefaultSelector which determines the best supported mechanism for I/O multiplexing on the runtime platform. The functionality is the same, but it works on a wider range of platforms.

After switching to selectors.DefaultSelector, I discovered that Windows also doesn't support I/O multiplexing using pipes or files -- only sockets. I removed the use of os.pipe for the shutdown mechanism. It was redundant anyway -- simply closing the listener socket in the shutdown method is sufficient to cause the selector to return. Subsequently, the call to socket.accept (on the closed listener socket) causes the loop in serve_forever to terminate as expected.

I tested the change on Windows, Mac OS X, and a Linux container, and it seems that it correctly supports all three platforms.

@aaugustin
Copy link
Member

Thank you for doing the research. I considered using the selectors module when I wrote that code but wasn't quite sure about the benefits of the additional abstraction layer.

@ceharris
Copy link
Contributor Author

With some additional testing today, I noticed that Mac OS X doesn't return from the select call when the listener socket is closed. I would swear that I tested that yesterday, but I must have fooled myself. I'm going to push an additional commit to conditionally include the os.pipe as before when sys.platform != win32. With that, the only change for platforms other than Windows will be the use of selector.DefaultSelector instead of select.poll.

@aaugustin
Copy link
Member

Mac OS X doesn't return from the select call when the listener socket is closed.

This is almost certainly why I bother with the os.pipe() in the first place. There are some subtle differences in how sockets behave on BSD/macOS and Linux.

On the Win32 platform, only sockets can be used with I/O
multiplexing (such as that performed by selectors.DefaultSelector);
the pipe cannot be added to the selector. However, on the win32
platform, simply closing the listener socket is enough to cause the
call to select to return -- the additional pipe is redundant.

On Mac OS X (and possibly other BSD derivatives), closing the listener
socket isn't enough. In the interest of maximum compatibility, we
simply disable the use of os.pipe on the Win32 platform.
@ceharris
Copy link
Contributor Author

I pushed an additional commit to restore the use of os.pipe unless the runtime platform is win32.

@ceharris
Copy link
Contributor Author

ceharris commented May 1, 2023

I see that the test coverage is now failing, but I'm uncertain how to address it. If you can give me a little guidance, I'll be happy to put in some time to see if I can fix it.

@aaugustin
Copy link
Member

aaugustin commented May 1, 2023

Let's add if sys.platform != "win32": between lines 63 and 64 here.

For the purposes of coverage testing, we cannot realistically get branch coverage here with a CI that runs on Linux and this isn't a sufficient reason to run coverage testing on Windows.

This should work without any changes to the test suite..

@aaugustin
Copy link
Member

As far as I understand, this issue makes the new sync API for servers unusable under Windows. I think this deserves a bugfix release. I can take care of backporting to the 11.x branch and making a 11.0.3 release.

@aaugustin
Copy link
Member

Don't bother with the changelog; I'll take care of it when I do the backport.

@ceharris
Copy link
Contributor Author

ceharris commented May 2, 2023

As far as I understand, this issue makes the new sync API for servers unusable under Windows.

This was indeed my motivation for fixing it.

@ceharris
Copy link
Contributor Author

ceharris commented May 6, 2023

Do you need anything else from me on this PR?

@aaugustin
Copy link
Member

Thank you, I don't need anything else. I was on holidays last week. Probably I'll do it today or tomorrow.

@aaugustin aaugustin merged commit 5aafc9e into python-websockets:main May 7, 2023
aaugustin pushed a commit that referenced this pull request May 7, 2023
…ti-platform support (#1349)

* use multiplatform selector instead of poll

* don't use os.pipe with the I/O multiplexing selector on win32

On the Win32 platform, only sockets can be used with I/O
multiplexing (such as that performed by selectors.DefaultSelector);
the pipe cannot be added to the selector. However, on the win32
platform, simply closing the listener socket is enough to cause the
call to select to return -- the additional pipe is redundant.

On Mac OS X (and possibly other BSD derivatives), closing the listener
socket isn't enough. In the interest of maximum compatibility, we
simply disable the use of os.pipe on the Win32 platform.

* exclude platform checks for win32 from coverage testing
@aaugustin aaugustin added the bug label May 7, 2023
@aaugustin
Copy link
Member

Version 11.0.3 will be available on PyPI as soon as https://github.com/python-websockets/websockets/actions/runs/4907582069 completes.

@ceharris
Copy link
Contributor Author

ceharris commented May 7, 2023

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants