root / trunk / twisted / internet / pollreactor.py

Revision 26118, 6.6 kB (checked in by exarkun, 5 months ago)

Merge internal-fd-tracking-3602

Author: exarkun, itamarst
Reviewer: therve
Fixes: #3602

Refactor the reactor's internal tracking of file descriptors, particular
with respect to "internal" file descriptors which are created and used by
the reactor itself, not by applications.

Line 
1 # Copyright (c) 2001-2009 Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4
5 """
6 A poll() based implementation of the twisted main loop.
7
8 To install the event loop (and you should do this before any connections,
9 listeners or connectors are added)::
10
11     from twisted.internet import pollreactor
12     pollreactor.install()
13 """
14
15 # System imports
16 import errno, sys
17 from select import error as SelectError, poll
18 from select import POLLIN, POLLOUT, POLLHUP, POLLERR, POLLNVAL
19
20 from zope.interface import implements
21
22 # Twisted imports
23 from twisted.python import log
24 from twisted.internet import main, posixbase, error
25 from twisted.internet.interfaces import IReactorFDSet
26
27 POLL_DISCONNECTED = (POLLHUP | POLLERR | POLLNVAL)
28
29
30 class PollReactor(posixbase.PosixReactorBase):
31     """
32     A reactor that uses poll(2).
33
34     @ivar _poller: A L{poll} which will be used to check for I/O
35         readiness.
36
37     @ivar _selectables: A dictionary mapping integer file descriptors to
38         instances of L{FileDescriptor} which have been registered with the
39         reactor.  All L{FileDescriptors} which are currently receiving read or
40         write readiness notifications will be present as values in this
41         dictionary.
42
43     @ivar _reads: A dictionary mapping integer file descriptors to arbitrary
44         values (this is essentially a set).  Keys in this dictionary will be
45         registered with C{_poller} for read readiness notifications which will
46         be dispatched to the corresponding L{FileDescriptor} instances in
47         C{_selectables}.
48
49     @ivar _writes: A dictionary mapping integer file descriptors to arbitrary
50         values (this is essentially a set).  Keys in this dictionary will be
51         registered with C{_poller} for write readiness notifications which will
52         be dispatched to the corresponding L{FileDescriptor} instances in
53         C{_selectables}.
54     """
55     implements(IReactorFDSet)
56
57     def __init__(self):
58         """
59         Initialize polling object, file descriptor tracking dictionaries, and
60         the base class.
61         """
62         self._poller = poll()
63         self._selectables = {}
64         self._reads = {}
65         self._writes = {}
66         posixbase.PosixReactorBase.__init__(self)
67
68
69     def _updateRegistration(self, fd):
70         """Register/unregister an fd with the poller."""
71         try:
72             self._poller.unregister(fd)
73         except KeyError:
74             pass
75
76         mask = 0
77         if fd in self._reads:
78             mask = mask | POLLIN
79         if fd in self._writes:
80             mask = mask | POLLOUT
81         if mask != 0:
82             self._poller.register(fd, mask)
83         else:
84             if fd in self._selectables:
85                 del self._selectables[fd]
86
87     def _dictRemove(self, selectable, mdict):
88         try:
89             # the easy way
90             fd = selectable.fileno()
91             # make sure the fd is actually real.  In some situations we can get
92             # -1 here.
93             mdict[fd]
94         except:
95             # the hard way: necessary because fileno() may disappear at any
96             # moment, thanks to python's underlying sockets impl
97             for fd, fdes in self._selectables.items():
98                 if selectable is fdes:
99                     break
100             else:
101                 # Hmm, maybe not the right course of action?  This method can't
102                 # fail, because it happens inside error detection...
103                 return
104         if fd in mdict:
105             del mdict[fd]
106             self._updateRegistration(fd)
107
108     def addReader(self, reader):
109         """Add a FileDescriptor for notification of data available to read.
110         """
111         fd = reader.fileno()
112         if fd not in self._reads:
113             self._selectables[fd] = reader
114             self._reads[fd] =  1
115             self._updateRegistration(fd)
116
117     def addWriter(self, writer):
118         """Add a FileDescriptor for notification of data available to write.
119         """
120         fd = writer.fileno()
121         if fd not in self._writes:
122             self._selectables[fd] = writer
123             self._writes[fd] =  1
124             self._updateRegistration(fd)
125
126     def removeReader(self, reader):
127         """Remove a Selectable for notification of data available to read.
128         """
129         return self._dictRemove(reader, self._reads)
130
131     def removeWriter(self, writer):
132         """Remove a Selectable for notification of data available to write.
133         """
134         return self._dictRemove(writer, self._writes)
135
136     def removeAll(self):
137         """
138         Remove all selectables, and return a list of them.
139         """
140         return self._removeAll(
141             [self._selectables[fd] for fd in self._reads],
142             [self._selectables[fd] for fd in self._writes])
143
144
145     def doPoll(self, timeout):
146         """Poll the poller for new events."""
147         if timeout is not None:
148             timeout = int(timeout * 1000) # convert seconds to milliseconds
149
150         try:
151             l = self._poller.poll(timeout)
152         except SelectError, e:
153             if e[0] == errno.EINTR:
154                 return
155             else:
156                 raise
157         _drdw = self._doReadOrWrite
158         for fd, event in l:
159             try:
160                 selectable = self._selectables[fd]
161             except KeyError:
162                 # Handles the infrequent case where one selectable's
163                 # handler disconnects another.
164                 continue
165             log.callWithLogger(selectable, _drdw, selectable, fd, event)
166
167     doIteration = doPoll
168
169     def _doReadOrWrite(self, selectable, fd, event):
170         why = None
171         inRead = False
172         if event & POLL_DISCONNECTED and not (event & POLLIN):
173             why = main.CONNECTION_LOST
174         else:
175             try:
176                 if event & POLLIN:
177                     why = selectable.doRead()
178                     inRead = True
179                 if not why and event & POLLOUT:
180                     why = selectable.doWrite()
181                     inRead = False
182                 if not selectable.fileno() == fd:
183                     why = error.ConnectionFdescWentAway('Filedescriptor went away')
184                     inRead = False
185             except:
186                 log.deferr()
187                 why = sys.exc_info()[1]
188         if why:
189             self._disconnectSelectable(selectable, why, inRead)
190
191
192     def getReaders(self):
193         return [self._selectables[fd] for fd in self._reads]
194
195
196     def getWriters(self):
197         return [self._selectables[fd] for fd in self._writes]
198
199
200
201 def install():
202     """Install the poll() reactor."""
203     p = PollReactor()
204     from twisted.internet.main import installReactor
205     installReactor(p)
206
207
208 __all__ = ["PollReactor", "install"]
Note: See TracBrowser for help on using the browser.