OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.test.test_internet -*- | |
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 """ | |
7 Select reactor | |
8 | |
9 Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} | |
10 """ | |
11 | |
12 from time import sleep | |
13 import sys | |
14 import select | |
15 from errno import EINTR, EBADF | |
16 | |
17 from zope.interface import implements | |
18 | |
19 from twisted.internet.interfaces import IReactorFDSet | |
20 from twisted.internet import error | |
21 from twisted.internet import posixbase | |
22 from twisted.python import log | |
23 from twisted.python.runtime import platformType | |
24 | |
25 | |
26 def win32select(r, w, e, timeout=None): | |
27 """Win32 select wrapper.""" | |
28 if not (r or w): | |
29 # windows select() exits immediately when no sockets | |
30 if timeout is None: | |
31 timeout = 0.01 | |
32 else: | |
33 timeout = min(timeout, 0.001) | |
34 sleep(timeout) | |
35 return [], [], [] | |
36 # windows doesn't process 'signals' inside select(), so we set a max | |
37 # time or ctrl-c will never be recognized | |
38 if timeout is None or timeout > 0.5: | |
39 timeout = 0.5 | |
40 r, w, e = select.select(r, w, w, timeout) | |
41 return r, w + e, [] | |
42 | |
43 if platformType == "win32": | |
44 _select = win32select | |
45 else: | |
46 _select = select.select | |
47 | |
48 # Exceptions that doSelect might return frequently | |
49 _NO_FILENO = error.ConnectionFdescWentAway('Handler has no fileno method') | |
50 _NO_FILEDESC = error.ConnectionFdescWentAway('Filedescriptor went away') | |
51 | |
52 class SelectReactor(posixbase.PosixReactorBase): | |
53 """ | |
54 A select() based reactor - runs on all POSIX platforms and on Win32. | |
55 | |
56 @ivar _reads: A dictionary mapping L{FileDescriptor} instances to arbitrary | |
57 values (this is essentially a set). Keys in this dictionary will be | |
58 checked for read events. | |
59 | |
60 @ivar _writes: A dictionary mapping L{FileDescriptor} instances to | |
61 arbitrary values (this is essentially a set). Keys in this dictionary | |
62 will be checked for writability. | |
63 """ | |
64 implements(IReactorFDSet) | |
65 | |
66 def __init__(self): | |
67 """ | |
68 Initialize file descriptor tracking dictionaries and the base class. | |
69 """ | |
70 self._reads = {} | |
71 self._writes = {} | |
72 posixbase.PosixReactorBase.__init__(self) | |
73 | |
74 | |
75 def _preenDescriptors(self): | |
76 log.msg("Malformed file descriptor found. Preening lists.") | |
77 readers = self._reads.keys() | |
78 writers = self._writes.keys() | |
79 self._reads.clear() | |
80 self._writes.clear() | |
81 for selDict, selList in ((self._reads, readers), | |
82 (self._writes, writers)): | |
83 for selectable in selList: | |
84 try: | |
85 select.select([selectable], [selectable], [selectable], 0) | |
86 except Exception, e: | |
87 log.msg("bad descriptor %s" % selectable) | |
88 self._disconnectSelectable(selectable, e, False) | |
89 else: | |
90 selDict[selectable] = 1 | |
91 | |
92 | |
93 def doSelect(self, timeout): | |
94 """ | |
95 Run one iteration of the I/O monitor loop. | |
96 | |
97 This will run all selectables who had input or output readiness | |
98 waiting for them. | |
99 """ | |
100 while 1: | |
101 try: | |
102 r, w, ignored = _select(self._reads.keys(), | |
103 self._writes.keys(), | |
104 [], timeout) | |
105 break | |
106 except ValueError, ve: | |
107 # Possibly a file descriptor has gone negative? | |
108 log.err() | |
109 self._preenDescriptors() | |
110 except TypeError, te: | |
111 # Something *totally* invalid (object w/o fileno, non-integral | |
112 # result) was passed | |
113 log.err() | |
114 self._preenDescriptors() | |
115 except (select.error, IOError), se: | |
116 # select(2) encountered an error | |
117 if se.args[0] in (0, 2): | |
118 # windows does this if it got an empty list | |
119 if (not self._reads) and (not self._writes): | |
120 return | |
121 else: | |
122 raise | |
123 elif se.args[0] == EINTR: | |
124 return | |
125 elif se.args[0] == EBADF: | |
126 self._preenDescriptors() | |
127 else: | |
128 # OK, I really don't know what's going on. Blow up. | |
129 raise | |
130 _drdw = self._doReadOrWrite | |
131 _logrun = log.callWithLogger | |
132 for selectables, method, fdset in ((r, "doRead", self._reads), | |
133 (w,"doWrite", self._writes)): | |
134 for selectable in selectables: | |
135 # if this was disconnected in another thread, kill it. | |
136 # ^^^^ --- what the !@#*? serious! -exarkun | |
137 if selectable not in fdset: | |
138 continue | |
139 # This for pausing input when we're not ready for more. | |
140 _logrun(selectable, _drdw, selectable, method, dict) | |
141 | |
142 doIteration = doSelect | |
143 | |
144 def _doReadOrWrite(self, selectable, method, dict): | |
145 try: | |
146 why = getattr(selectable, method)() | |
147 handfn = getattr(selectable, 'fileno', None) | |
148 if not handfn: | |
149 why = _NO_FILENO | |
150 elif handfn() == -1: | |
151 why = _NO_FILEDESC | |
152 except: | |
153 why = sys.exc_info()[1] | |
154 log.err() | |
155 if why: | |
156 self._disconnectSelectable(selectable, why, method=="doRead") | |
157 | |
158 def addReader(self, reader): | |
159 """ | |
160 Add a FileDescriptor for notification of data available to read. | |
161 """ | |
162 self._reads[reader] = 1 | |
163 | |
164 def addWriter(self, writer): | |
165 """ | |
166 Add a FileDescriptor for notification of data available to write. | |
167 """ | |
168 self._writes[writer] = 1 | |
169 | |
170 def removeReader(self, reader): | |
171 """ | |
172 Remove a Selectable for notification of data available to read. | |
173 """ | |
174 if reader in self._reads: | |
175 del self._reads[reader] | |
176 | |
177 def removeWriter(self, writer): | |
178 """ | |
179 Remove a Selectable for notification of data available to write. | |
180 """ | |
181 if writer in self._writes: | |
182 del self._writes[writer] | |
183 | |
184 def removeAll(self): | |
185 return self._removeAll(self._reads, self._writes) | |
186 | |
187 | |
188 def getReaders(self): | |
189 return self._reads.keys() | |
190 | |
191 | |
192 def getWriters(self): | |
193 return self._writes.keys() | |
194 | |
195 | |
196 | |
197 def install(): | |
198 """Configure the twisted mainloop to be run using the select() reactor. | |
199 """ | |
200 reactor = SelectReactor() | |
201 from twisted.internet.main import installReactor | |
202 installReactor(reactor) | |
203 | |
204 __all__ = ['install'] | |
OLD | NEW |