blob: bbeada8772c5f586b4233a661061bd95a2b34a83 [file] [log] [blame]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07001"""Selector and proactor eventloops for Windows."""
2
3import errno
4import socket
5import weakref
6import struct
7import _winapi
8
9from . import futures
10from . import proactor_events
11from . import selector_events
12from . import tasks
13from . import windows_utils
Guido van Rossumfc29e0f2013-10-17 15:39:45 -070014from .log import logger
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070015
16try:
17 import _overlapped
18except ImportError:
19 from . import _overlapped
20
21
22__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor']
23
24
25NULL = 0
26INFINITE = 0xffffffff
27ERROR_CONNECTION_REFUSED = 1225
28ERROR_CONNECTION_ABORTED = 1236
29
30
31class _OverlappedFuture(futures.Future):
32 """Subclass of Future which represents an overlapped operation.
33
34 Cancelling it will immediately cancel the overlapped operation.
35 """
36
37 def __init__(self, ov, *, loop=None):
38 super().__init__(loop=loop)
39 self.ov = ov
40
41 def cancel(self):
42 try:
43 self.ov.cancel()
44 except OSError:
45 pass
46 return super().cancel()
47
48
49class PipeServer(object):
50 """Class representing a pipe server.
51
52 This is much like a bound, listening socket.
53 """
54 def __init__(self, address):
55 self._address = address
56 self._free_instances = weakref.WeakSet()
57 self._pipe = self._server_pipe_handle(True)
58
59 def _get_unconnected_pipe(self):
60 # Create new instance and return previous one. This ensures
61 # that (until the server is closed) there is always at least
62 # one pipe handle for address. Therefore if a client attempt
63 # to connect it will not fail with FileNotFoundError.
64 tmp, self._pipe = self._pipe, self._server_pipe_handle(False)
65 return tmp
66
67 def _server_pipe_handle(self, first):
68 # Return a wrapper for a new pipe handle.
69 if self._address is None:
70 return None
71 flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
72 if first:
73 flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
74 h = _winapi.CreateNamedPipe(
75 self._address, flags,
76 _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
77 _winapi.PIPE_WAIT,
78 _winapi.PIPE_UNLIMITED_INSTANCES,
79 windows_utils.BUFSIZE, windows_utils.BUFSIZE,
80 _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
81 pipe = windows_utils.PipeHandle(h)
82 self._free_instances.add(pipe)
83 return pipe
84
85 def close(self):
86 # Close all instances which have not been connected to by a client.
87 if self._address is not None:
88 for pipe in self._free_instances:
89 pipe.close()
90 self._pipe = None
91 self._address = None
92 self._free_instances.clear()
93
94 __del__ = close
95
96
97class SelectorEventLoop(selector_events.BaseSelectorEventLoop):
98 """Windows version of selector event loop."""
99
100 def _socketpair(self):
101 return windows_utils.socketpair()
102
103
104class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
105 """Windows version of proactor event loop using IOCP."""
106
107 def __init__(self, proactor=None):
108 if proactor is None:
109 proactor = IocpProactor()
110 super().__init__(proactor)
111
112 def _socketpair(self):
113 return windows_utils.socketpair()
114
115 @tasks.coroutine
116 def create_pipe_connection(self, protocol_factory, address):
117 f = self._proactor.connect_pipe(address)
118 pipe = yield from f
119 protocol = protocol_factory()
120 trans = self._make_duplex_pipe_transport(pipe, protocol,
121 extra={'addr': address})
122 return trans, protocol
123
124 @tasks.coroutine
125 def start_serving_pipe(self, protocol_factory, address):
126 server = PipeServer(address)
127 def loop(f=None):
128 pipe = None
129 try:
130 if f:
131 pipe = f.result()
132 server._free_instances.discard(pipe)
133 protocol = protocol_factory()
134 self._make_duplex_pipe_transport(
135 pipe, protocol, extra={'addr': address})
136 pipe = server._get_unconnected_pipe()
137 if pipe is None:
138 return
139 f = self._proactor.accept_pipe(pipe)
140 except OSError:
141 if pipe and pipe.fileno() != -1:
Guido van Rossumfc29e0f2013-10-17 15:39:45 -0700142 logger.exception('Pipe accept failed')
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700143 pipe.close()
144 except futures.CancelledError:
145 if pipe:
146 pipe.close()
147 else:
148 f.add_done_callback(loop)
149 self.call_soon(loop)
150 return [server]
151
152 def _stop_serving(self, server):
153 server.close()
154
155
156class IocpProactor:
157 """Proactor implementation using IOCP."""
158
159 def __init__(self, concurrency=0xffffffff):
160 self._loop = None
161 self._results = []
162 self._iocp = _overlapped.CreateIoCompletionPort(
163 _overlapped.INVALID_HANDLE_VALUE, NULL, 0, concurrency)
164 self._cache = {}
165 self._registered = weakref.WeakSet()
166 self._stopped_serving = weakref.WeakSet()
167
168 def set_loop(self, loop):
169 self._loop = loop
170
171 def select(self, timeout=None):
172 if not self._results:
173 self._poll(timeout)
174 tmp = self._results
175 self._results = []
176 return tmp
177
178 def recv(self, conn, nbytes, flags=0):
179 self._register_with_iocp(conn)
180 ov = _overlapped.Overlapped(NULL)
181 if isinstance(conn, socket.socket):
182 ov.WSARecv(conn.fileno(), nbytes, flags)
183 else:
184 ov.ReadFile(conn.fileno(), nbytes)
185 def finish(trans, key, ov):
186 try:
187 return ov.getresult()
188 except OSError as exc:
189 if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
190 raise ConnectionResetError(*exc.args)
191 else:
192 raise
193 return self._register(ov, conn, finish)
194
195 def send(self, conn, buf, flags=0):
196 self._register_with_iocp(conn)
197 ov = _overlapped.Overlapped(NULL)
198 if isinstance(conn, socket.socket):
199 ov.WSASend(conn.fileno(), buf, flags)
200 else:
201 ov.WriteFile(conn.fileno(), buf)
202 def finish(trans, key, ov):
203 try:
204 return ov.getresult()
205 except OSError as exc:
206 if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
207 raise ConnectionResetError(*exc.args)
208 else:
209 raise
210 return self._register(ov, conn, finish)
211
212 def accept(self, listener):
213 self._register_with_iocp(listener)
214 conn = self._get_accept_socket(listener.family)
215 ov = _overlapped.Overlapped(NULL)
216 ov.AcceptEx(listener.fileno(), conn.fileno())
217 def finish_accept(trans, key, ov):
218 ov.getresult()
219 # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work.
220 buf = struct.pack('@P', listener.fileno())
221 conn.setsockopt(socket.SOL_SOCKET,
222 _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf)
223 conn.settimeout(listener.gettimeout())
224 return conn, conn.getpeername()
225 return self._register(ov, listener, finish_accept)
226
227 def connect(self, conn, address):
228 self._register_with_iocp(conn)
229 # The socket needs to be locally bound before we call ConnectEx().
230 try:
231 _overlapped.BindLocal(conn.fileno(), conn.family)
232 except OSError as e:
233 if e.winerror != errno.WSAEINVAL:
234 raise
235 # Probably already locally bound; check using getsockname().
236 if conn.getsockname()[1] == 0:
237 raise
238 ov = _overlapped.Overlapped(NULL)
239 ov.ConnectEx(conn.fileno(), address)
240 def finish_connect(trans, key, ov):
241 ov.getresult()
242 # Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work.
243 conn.setsockopt(socket.SOL_SOCKET,
244 _overlapped.SO_UPDATE_CONNECT_CONTEXT, 0)
245 return conn
246 return self._register(ov, conn, finish_connect)
247
248 def accept_pipe(self, pipe):
249 self._register_with_iocp(pipe)
250 ov = _overlapped.Overlapped(NULL)
251 ov.ConnectNamedPipe(pipe.fileno())
252 def finish(trans, key, ov):
253 ov.getresult()
254 return pipe
255 return self._register(ov, pipe, finish)
256
257 def connect_pipe(self, address):
258 ov = _overlapped.Overlapped(NULL)
259 ov.WaitNamedPipeAndConnect(address, self._iocp, ov.address)
260 def finish(err, handle, ov):
261 # err, handle were arguments passed to PostQueuedCompletionStatus()
262 # in a function run in a thread pool.
263 if err == _overlapped.ERROR_SEM_TIMEOUT:
264 # Connection did not succeed within time limit.
265 msg = _overlapped.FormatMessage(err)
266 raise ConnectionRefusedError(0, msg, None, err)
267 elif err != 0:
268 msg = _overlapped.FormatMessage(err)
269 raise OSError(0, msg, None, err)
270 else:
271 return windows_utils.PipeHandle(handle)
272 return self._register(ov, None, finish, wait_for_post=True)
273
274 def _register_with_iocp(self, obj):
275 # To get notifications of finished ops on this objects sent to the
276 # completion port, were must register the handle.
277 if obj not in self._registered:
278 self._registered.add(obj)
279 _overlapped.CreateIoCompletionPort(obj.fileno(), self._iocp, 0, 0)
280 # XXX We could also use SetFileCompletionNotificationModes()
281 # to avoid sending notifications to completion port of ops
282 # that succeed immediately.
283
284 def _register(self, ov, obj, callback, wait_for_post=False):
285 # Return a future which will be set with the result of the
286 # operation when it completes. The future's value is actually
287 # the value returned by callback().
288 f = _OverlappedFuture(ov, loop=self._loop)
289 if ov.pending or wait_for_post:
290 # Register the overlapped operation for later. Note that
291 # we only store obj to prevent it from being garbage
292 # collected too early.
293 self._cache[ov.address] = (f, ov, obj, callback)
294 else:
295 # The operation has completed, so no need to postpone the
296 # work. We cannot take this short cut if we need the
297 # NumberOfBytes, CompletionKey values returned by
298 # PostQueuedCompletionStatus().
299 try:
300 value = callback(None, None, ov)
301 except OSError as e:
302 f.set_exception(e)
303 else:
304 f.set_result(value)
305 return f
306
307 def _get_accept_socket(self, family):
308 s = socket.socket(family)
309 s.settimeout(0)
310 return s
311
312 def _poll(self, timeout=None):
313 if timeout is None:
314 ms = INFINITE
315 elif timeout < 0:
316 raise ValueError("negative timeout")
317 else:
318 ms = int(timeout * 1000 + 0.5)
319 if ms >= INFINITE:
320 raise ValueError("timeout too big")
321 while True:
322 status = _overlapped.GetQueuedCompletionStatus(self._iocp, ms)
323 if status is None:
324 return
325 err, transferred, key, address = status
326 try:
327 f, ov, obj, callback = self._cache.pop(address)
328 except KeyError:
329 # key is either zero, or it is used to return a pipe
330 # handle which should be closed to avoid a leak.
331 if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
332 _winapi.CloseHandle(key)
333 ms = 0
334 continue
335 if obj in self._stopped_serving:
336 f.cancel()
337 elif not f.cancelled():
338 try:
339 value = callback(transferred, key, ov)
340 except OSError as e:
341 f.set_exception(e)
342 self._results.append(f)
343 else:
344 f.set_result(value)
345 self._results.append(f)
346 ms = 0
347
348 def _stop_serving(self, obj):
349 # obj is a socket or pipe handle. It will be closed in
350 # BaseProactorEventLoop._stop_serving() which will make any
351 # pending operations fail quickly.
352 self._stopped_serving.add(obj)
353
354 def close(self):
355 # Cancel remaining registered operations.
356 for address, (f, ov, obj, callback) in list(self._cache.items()):
357 if obj is None:
358 # The operation was started with connect_pipe() which
359 # queues a task to Windows' thread pool. This cannot
360 # be cancelled, so just forget it.
361 del self._cache[address]
362 else:
363 try:
364 ov.cancel()
365 except OSError:
366 pass
367
368 while self._cache:
369 if not self._poll(1):
Guido van Rossumfc29e0f2013-10-17 15:39:45 -0700370 logger.debug('taking long time to close proactor')
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700371
372 self._results = []
373 if self._iocp is not None:
374 _winapi.CloseHandle(self._iocp)
375 self._iocp = None