2d75dc1e2f23bf4b52c0184f445eed9e3dad1c67
[trex.git] /
1 """Lowest-common-denominator implementations of platform functionality."""
2 from __future__ import absolute_import, division, print_function, with_statement
3
4 import errno
5 import socket
6
7 from . import interface
8
9
10 class Waker(interface.Waker):
11     """Create an OS independent asynchronous pipe.
12
13     For use on platforms that don't have os.pipe() (or where pipes cannot
14     be passed to select()), but do have sockets.  This includes Windows
15     and Jython.
16     """
17     def __init__(self):
18         # Based on Zope async.py: http://svn.zope.org/zc.ngi/trunk/src/zc/ngi/async.py
19
20         self.writer = socket.socket()
21         # Disable buffering -- pulling the trigger sends 1 byte,
22         # and we want that sent immediately, to wake up ASAP.
23         self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
24
25         count = 0
26         while 1:
27             count += 1
28             # Bind to a local port; for efficiency, let the OS pick
29             # a free port for us.
30             # Unfortunately, stress tests showed that we may not
31             # be able to connect to that port ("Address already in
32             # use") despite that the OS picked it.  This appears
33             # to be a race bug in the Windows socket implementation.
34             # So we loop until a connect() succeeds (almost always
35             # on the first try).  See the long thread at
36             # http://mail.zope.org/pipermail/zope/2005-July/160433.html
37             # for hideous details.
38             a = socket.socket()
39             a.bind(("127.0.0.1", 0))
40             a.listen(1)
41             connect_address = a.getsockname()  # assigned (host, port) pair
42             try:
43                 self.writer.connect(connect_address)
44                 break    # success
45             except socket.error as detail:
46                 if (not hasattr(errno, 'WSAEADDRINUSE') or
47                         detail[0] != errno.WSAEADDRINUSE):
48                     # "Address already in use" is the only error
49                     # I've seen on two WinXP Pro SP2 boxes, under
50                     # Pythons 2.3.5 and 2.4.1.
51                     raise
52                 # (10048, 'Address already in use')
53                 # assert count <= 2 # never triggered in Tim's tests
54                 if count >= 10:  # I've never seen it go above 2
55                     a.close()
56                     self.writer.close()
57                     raise socket.error("Cannot bind trigger!")
58                 # Close `a` and try again.  Note:  I originally put a short
59                 # sleep() here, but it didn't appear to help or hurt.
60                 a.close()
61
62         self.reader, addr = a.accept()
63         self.reader.setblocking(0)
64         self.writer.setblocking(0)
65         a.close()
66         self.reader_fd = self.reader.fileno()
67
68     def fileno(self):
69         return self.reader.fileno()
70
71     def write_fileno(self):
72         return self.writer.fileno()
73
74     def wake(self):
75         try:
76             self.writer.send(b"x")
77         except (IOError, socket.error):
78             pass
79
80     def consume(self):
81         try:
82             while True:
83                 result = self.reader.recv(1024)
84                 if not result:
85                     break
86         except (IOError, socket.error):
87             pass
88
89     def close(self):
90         self.reader.close()
91         self.writer.close()