Skip to content

Commit

Permalink
Use selectors instead of select.poll in sync.WebSocket Server for mul…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
ceharris committed May 7, 2023
1 parent e152ced commit 5aafc9e
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 8 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ source = [
exclude_lines = [
"except ImportError:",
"if self.debug:",
"if sys.platform != \"win32\":",
"pragma: no cover",
"raise AssertionError",
"raise NotImplementedError",
Expand Down
20 changes: 12 additions & 8 deletions src/websockets/sync/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import http
import logging
import os
import select
import selectors
import socket
import ssl
import sys
import threading
from types import TracebackType
from typing import Any, Callable, Optional, Sequence, Type
Expand Down Expand Up @@ -199,7 +200,8 @@ def __init__(
if logger is None:
logger = logging.getLogger("websockets.server")
self.logger = logger
self.shutdown_watcher, self.shutdown_notifier = os.pipe()
if sys.platform != "win32":
self.shutdown_watcher, self.shutdown_notifier = os.pipe()

def serve_forever(self) -> None:
"""
Expand All @@ -214,15 +216,16 @@ def serve_forever(self) -> None:
server.serve_forever()
"""
poller = select.poll()
poller.register(self.socket)
poller.register(self.shutdown_watcher)
poller = selectors.DefaultSelector()
poller.register(self.socket, selectors.EVENT_READ)
if sys.platform != "win32":
poller.register(self.shutdown_watcher, selectors.EVENT_READ)

while True:
poller.poll()
poller.select()
try:
# If the socket is closed, this will raise an exception and exit
# the loop. So we don't need to check the return value of poll().
# the loop. So we don't need to check the return value of select().
sock, addr = self.socket.accept()
except OSError:
break
Expand All @@ -235,7 +238,8 @@ def shutdown(self) -> None:
"""
self.socket.close()
os.write(self.shutdown_notifier, b"x")
if sys.platform != "win32":
os.write(self.shutdown_notifier, b"x")

def fileno(self) -> int:
"""
Expand Down

0 comments on commit 5aafc9e

Please sign in to comment.