Repository: andreikop/python-ws-discovery (language: python)

Issue:
While running it in windows. select.poll() is not working.
While running it in windows. select.poll() is not working.
Any work around ?



Discussion / hints:
@rsmrahul1 see #4 and the fork by @tran-wang.

Great to see you guys have continued the project with bug fixes and all :) .. After the initial project done in google code, I couldn't really look into it. I did the project as part of a project in my undergraduate degree. Glad to see it is useful to others too.
I suggest we close this. This package claims no Windows support: in trove classifiers there's Linux & MacOS only. The forks that presumably work in Windows seem to merely drop the functionality that uses `select.poll`, rather than implement any Windows-specific replacement for it.
I keep it open to
1. avoid reopening by new reporters
2. with hope that somebody will fix it sometimes
But, I don't mind closing.
Wouldn't avoiding new "Windows support" issues be best handled by documentation? The package is not really explicit about that at the moment, which imho is why duplicates have popped up.

As for adding better Windows support, looks like it might be possible to take advantage of pywin32 IOCP support. The pycos package (https://pypi.org/project/pycos/) uses it internally and might thus be a good place to start from. Based on some googling, this is pretty much the only way to go. 

But on the other hand, if we could decouple the threaded networking implementation from the rest of the code and then add asyncio support, that would "fix" the issue, too.

PR which adds to README.md a section with explanation of Windows problems and ideas how some contributor can fix this problems is welcome!
> PR which adds to README.md a section with explanation of Windows problems and ideas how some contributor can fix this problems is welcome!

I ported to Python 3.8 for Windows 7+
NOT tested on POSIX systems
numerous changes to fix deprecations in Py38
[WSDiscovery.zip](https://github.com/andreikop/python-ws-discovery/files/4361013/WSDiscovery.zip)


Fix patch:
diff --git a/wsdiscovery/threaded.py b/wsdiscovery/threaded.py
index 270ab44..677a133 100644
--- a/wsdiscovery/threaded.py
+++ b/wsdiscovery/threaded.py
@@ -7,7 +7,7 @@
 import socket
 import struct
 import threading
-import select
+import selectors
 
 from .udp import UDPMessage
 from .actions import *
@@ -83,7 +83,7 @@ def __init__(self, observer, capture=None):
         self._observer = observer
         self._capture = observer._capture
         self._seqnum = 1 # capture sequence number
-        self._poll = select.poll()
+        self._selector = selectors.DefaultSelector()
 
     @staticmethod
     def _makeMreq(addr):
@@ -107,7 +107,6 @@ def _createMulticastInSocket():
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
         sock.bind(('', MULTICAST_PORT))
-
         sock.setblocking(0)
 
         return sock
@@ -121,7 +120,7 @@ def addSourceAddr(self, addr):
 
         sock = self._createMulticastOutSocket(addr, self._observer.ttl)
         self._multiOutUniInSockets[addr] = sock
-        self._poll.register(sock, select.POLLIN)
+        self._selector.register(sock, selectors.EVENT_READ)
 
     def removeSourceAddr(self, addr):
         try:
@@ -130,7 +129,7 @@ def removeSourceAddr(self, addr):
             pass
 
         sock = self._multiOutUniInSockets[addr]
-        self._poll.unregister(sock)
+        self._selector.unregister(sock)
         sock.close()
         del self._multiOutUniInSockets[addr]
 
@@ -152,8 +151,8 @@ def run(self):
             self._recvMessages()
 
     def _recvMessages(self):
-        for fd, event in self._poll.poll(0):
-            sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_DGRAM)
+        for key, events in self._selector.select(0):
+            sock = socket.fromfd(key.fd, socket.AF_INET, socket.SOCK_DGRAM)
             try:
                 data, addr = sock.recvfrom(BUFFER_SIZE)
             except socket.error as e:
@@ -238,7 +237,7 @@ def start(self):
         self._uniOutSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 
         self._multiInSocket = self._createMulticastInSocket()
-        self._poll.register(self._multiInSocket)
+        self._selector.register(self._multiInSocket, selectors.EVENT_WRITE | selectors.EVENT_READ)
 
         self._multiOutUniInSockets = {}  # FIXME synchronisation
 
@@ -246,7 +245,7 @@ def join(self):
         super(NetworkingThread, self).join()
         self._uniOutSocket.close()
 
-        self._poll.unregister(self._multiInSocket)
+        self._selector.unregister(self._multiInSocket)
         self._multiInSocket.close()
 
 


Test patch:
diff --git a/tests/test_probing.py b/tests/test_probing.py
index 4ffc1c4..a59959c 100644
--- a/tests/test_probing.py
+++ b/tests/test_probing.py
@@ -1,25 +1,49 @@
-import select
+import logging
+import selectors
 import socket
-from .fixtures import wsd, probe_response
-from mock import patch
+from .fixtures import probe_response
+from wsdiscovery.threaded import NetworkingThread, MULTICAST_PORT
+from wsdiscovery import WSDiscovery
 
 
-@patch('select.poll')
-@patch('socket.socket.recvfrom')
-def test_probing(mock_recv, mock_poll, wsd, probe_response):
+def test_probing(monkeypatch, probe_response):
+    "mock up socket registration, event selection & socket message response"
 
-    def mock_poll_fd(*args, **kwargs):
-        "set correct poll.poll() response"
-        sck = args[0] # poll.register is called with socket as a parameter
-        if sck.proto == socket.IPPROTO_UDP:
-            fn = sck.fileno()
-            # mock up a claim that the registered fd has data
-            mock_poll.return_value.poll.return_value = [(fn, select.POLLIN)]
+    sck = None
 
-    mock_poll.return_value.register.side_effect = mock_poll_fd
-    mock_recv.return_value = probe_response
+    def mock_register(selector, rsock, evtmask):
+        """get hold of the multicast socket that will send the Probe message,
+        when the socket is registered with the selector"""
+        global sck
+        # The Probe is sent multicast, we use that to identify the right socket.
+        # Identification could be done better, but this is enough for now.
+        if rsock.getsockname()[1] == MULTICAST_PORT:
+            sck = rsock
 
+    def mock_select(*args):
+        "set a mock Probe response event in motion for the same socket"
+        global sck
+        if sck and sck.getsockname()[1] == MULTICAST_PORT:
+            key = selectors.SelectorKey(sck.makefile(), sck.fileno(), [], "")
+            # to mock just one response we just nullify the sock
+            sck = None
+            return [(key, selectors.EVENT_READ)]
+        else:
+            return []
+
+    def mock_recvfrom(*args):
+        return probe_response
+
+    monkeypatch.setattr(selectors.DefaultSelector, "register", mock_register)
+    monkeypatch.setattr(selectors.DefaultSelector, "select", mock_select)
+    monkeypatch.setattr(socket.socket, "recvfrom", mock_recvfrom)
+
+    # we cannot use a fixture that'd start discovery for us, since the socket
+    # selector registration happens at startup time
+    wsd = WSDiscovery()
+    wsd.start()
     found = wsd.searchServices()
+
     assert len(found) == 1
     assert probe_response[1] in found[0].getXAddrs()[0]
     assert len(found[0].getScopes()) == 4