Repository: tornadoweb/tornado

Issue:
Subprocess wait_for_exit Error on Windows
If I create a tornado Subprocess and wait for it to exit, I get this error: 

> ERROR:tornado.application:Future exception was never retrieved: Traceback (most recent call last):
> File "C:\Python27\Lib\site-packages\tornado\gen.py", line 230, in wrapper
>    yielded = next(result)
> File "C:\test.py",
>    yield proc.wait_for_exit()
> File "C:\Python27\Lib\site-packages\tornado\process.py", line 290, in wait_for_exit
>    self.set_exit_callback(callback)
> File "C:\Python27\Lib\site-packages\tornado\process.py", line 262, in set_exit_callback
>    Subprocess.initialize(self.io_loop)
> File "C:\Python27\Lib\site-packages\tornado\process.py", line 310, in initialize
>    signal.SIGCHLD,
> AttributeError: 'module' object has no attribute 'SIGCHLD'

So I guess Windows does not have SIGCHLD. Does this mean wait_for_exit is not supported on Windows?



Discussion:
Probably not. 

As an alternative, pass `stdout=tornado.process.STREAM` when creating the process and use `yield proc.stdout.read_until_close()`. In most cases a child process will close stdout when it exits, so this will be essentially equivalent. 

Thanks for the quick reply and the workaround. Unfortunately the workaround leads to another Windows related error: 

> ERROR:tornado.application:Future exception was never retrieved: Traceback (most recent call last):
> File "C:\Python27\Lib\site-packages\tornado\gen.py", line 230, in wrapper
> yielded = next(result)
> File "C:\test.py"
> proc = process.Subprocess(args, stdout=process.Subprocess.STREAM, startupinfo=startupinfo)
> File "C:\Python27\Lib\site-packages\tornado\process.py", line 221, in **init**
> out_r, out_w = _pipe_cloexec()
> File "C:\Python27\Lib\site-packages\tornado\process.py", line 88, in _pipe_cloexec
> set_close_exec(r)
> File "C:\Python27\Lib\site-packages\tornado\platform\windows.py", line 21, in set_close_exec
> raise ctypes.GetLastError()
> TypeError: exceptions must be old-style classes or derived from BaseException, not int

Hmm, that's a strange error. I'm not sure how hard it would be to fix, but after that you run into the fact that `select()` on windows can't handle pipes, so `PipeIOStream` just won't work there. Looks like we just need to document `tornado.process.Subprocess` as posix-only since there is no meaningful part of it that works on windows.

It looks like to me that tornado\platform/windows.py has a mistake. It should be raise ctypes.WinError(), not raise ctypes.GetLastError()


Resolving patch:
diff --git a/tornado/platform/windows.py b/tornado/platform/windows.py
--- a/tornado/platform/windows.py
+++ b/tornado/platform/windows.py
@@ -17,4 +17,4 @@
 def set_close_exec(fd):
     success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 0)
     if not success:
-        raise ctypes.GetLastError()
+        raise ctypes.WinError()
diff --git a/tornado/process.py b/tornado/process.py
--- a/tornado/process.py
+++ b/tornado/process.py
@@ -201,8 +201,14 @@ class Subprocess(object):
       attribute of the resulting Subprocess a `.PipeIOStream`.
     * A new keyword argument ``io_loop`` may be used to pass in an IOLoop.
 
+    The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and
+    ``wait_for_exit`` methods do not work on Windows. There is
+    therefore no reason to use this class instead of
+    ``subprocess.Popen`` on that platform.
+
     .. versionchanged:: 4.1
        The ``io_loop`` argument is deprecated.
+
     """
     STREAM = object()
 


Test patch:
diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py
--- a/tornado/test/runtests.py
+++ b/tornado/test/runtests.py
@@ -53,6 +53,7 @@
     'tornado.test.util_test',
     'tornado.test.web_test',
     'tornado.test.websocket_test',
+    'tornado.test.windows_test',
     'tornado.test.wsgi_test',
 ]
 
diff --git a/tornado/test/windows_test.py b/tornado/test/windows_test.py
new file mode 100644
--- /dev/null
+++ b/tornado/test/windows_test.py
@@ -0,0 +1,25 @@
+import functools
+import os
+import socket
+import unittest
+
+from tornado.platform.auto import set_close_exec
+
+skipIfNonWindows = unittest.skipIf(os.name != 'nt', 'non-windows platform')
+
+@skipIfNonWindows
+class WindowsTest(unittest.TestCase):
+    def test_set_close_exec(self):
+        # set_close_exec works with sockets.
+        s = socket.socket()
+        self.addCleanup(s.close)
+        set_close_exec(s.fileno())
+
+        # But it doesn't work with pipes.
+        r, w = os.pipe()
+        self.addCleanup(functools.partial(os.close, r))
+        self.addCleanup(functools.partial(os.close, w))
+        with self.assertRaises(WindowsError) as cm:
+            set_close_exec(r)
+        ERROR_INVALID_HANDLE = 6
+        self.assertEqual(cm.exception.winerror, ERROR_INVALID_HANDLE)