Repository: tornadoweb/tornado

Issue:
Windows doesn't use standard error numbers: suggested quick fix
Windows (in my case: Windows 7, 64-bit and Visual Studio 2012) does not use the standard socket error numbers (e.g. `errno.EWOULDBLOCK`). Instead, there are corresponding error numbers prefixed with `WSA` (e.g. `errno.WSAEWOULDBLOCK`). Since tornado only checks for the standard ones, its test suite (`runtests.sh`) fails on Windows. 

I was able to get the test suite running with the following quick fix: I first added a file `tornado/portable_errno.py` containing

``` python
import errno
from errno import *

for _k in dir(errno):
    if _k.startswith('WSA'):
        _e = _k[3:]
        if hasattr(errno, _e):
            # override standard error code with corresponding Windows code
            exec(_e + ' = ' + _k)
```

and then changed every occurence of `import errno` in the other source files with

``` python
import tornado.portable_errno as errno
```

However, it is probably cleaner to check for both types of errors instead of just overriding.



Discussion:
Python 2.7 only officially supports building with Visual Studio 2008; I think this is one of the consequences of building with a newer SDK.  The E and WSAE constants are supposed to be aliases for the same values.  I'm not sure if there are other consequences of building 2.7 with the new SDK, but if things are otherwise working I'd accept a patch that added the WSAE equivalents to constants like iostream._ERRNO_WOULDBLOCK and _ERRNO_CONNRESET.


Resolving patch:
diff --git a/tornado/iostream.py b/tornado/iostream.py
--- a/tornado/iostream.py
+++ b/tornado/iostream.py
@@ -57,12 +57,24 @@
 # some they differ.
 _ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
 
+if hasattr(errno, "WSAEWOULDBLOCK"):
+    _ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,)
+
 # These errnos indicate that a connection has been abruptly terminated.
 # They should be caught and handled less noisily than other errors.
 _ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE,
                     errno.ETIMEDOUT)
 
+if hasattr(errno, "WSAECONNRESET"):
+    _ERRNO_CONNRESET += (errno.WSAECONNRESET, errno.WSAECONNABORTED, errno.WSAETIMEDOUT)
+
+# More non-portable errnos:
+_ERRNO_INPROGRESS = (errno.EINPROGRESS,)
+
+if hasattr(errno, "WSAEINPROGRESS"):
+    _ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,)
 
+#######################################################
 class StreamClosedError(IOError):
     """Exception raised by `IOStream` methods when the stream is closed.
 
@@ -990,7 +1002,7 @@ class is recommended instead of calling this method directly.
             # returned immediately when attempting to connect to
             # localhost, so handle them the same way as an error
             # reported later in _handle_connect.
-            if (errno_from_exception(e) != errno.EINPROGRESS and
+            if (errno_from_exception(e) not in _ERRNO_INPROGRESS and
                     errno_from_exception(e) not in _ERRNO_WOULDBLOCK):
                 gen_log.warning("Connect error on fd %s: %s",
                                 self.socket.fileno(), e)
diff --git a/tornado/netutil.py b/tornado/netutil.py
--- a/tornado/netutil.py
+++ b/tornado/netutil.py
@@ -57,6 +57,9 @@
 # some they differ.
 _ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
 
+if hasattr(errno, "WSAEWOULDBLOCK"):
+    _ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,)
+
 
 def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128, flags=None):
     """Creates listening sockets bound to the given port and address.


Test patch:
diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py
--- a/tornado/test/iostream_test.py
+++ b/tornado/test/iostream_test.py
@@ -232,8 +232,11 @@ def connect_callback():
         self.assertFalse(self.connect_called)
         self.assertTrue(isinstance(stream.error, socket.error), stream.error)
         if sys.platform != 'cygwin':
+            _ERRNO_CONNREFUSED = (errno.ECONNREFUSED,)
+            if hasattr(errno, "WSAECONNREFUSED"):
+                _ERRNO_CONNREFUSED += (errno.WSAECONNREFUSED,)
             # cygwin's errnos don't match those used on native windows python
-            self.assertEqual(stream.error.args[0], errno.ECONNREFUSED)
+            self.assertTrue(stream.error.args[0] in _ERRNO_CONNREFUSED)
 
     def test_gaierror(self):
         # Test that IOStream sets its exc_info on getaddrinfo error
diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py
--- a/tornado/test/simple_httpclient_test.py
+++ b/tornado/test/simple_httpclient_test.py
@@ -321,8 +321,10 @@ def test_connection_refused(self):
 
         if sys.platform != 'cygwin':
             # cygwin returns EPERM instead of ECONNREFUSED here
-            self.assertTrue(str(errno.ECONNREFUSED) in str(response.error),
-                            response.error)
+            contains_errno = str(errno.ECONNREFUSED) in str(response.error)
+            if not contains_errno and hasattr(errno, "WSAECONNREFUSED"):
+                contains_errno = str(errno.WSAECONNREFUSED) in str(response.error)
+            self.assertTrue(contains_errno, response.error)
             # This is usually "Connection refused".
             # On windows, strerror is broken and returns "Unknown error".
             expected_message = os.strerror(errno.ECONNREFUSED)