root / trunk / twisted / internet / gtkreactor.py

Revision 26118, 7.3 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 This module provides support for Twisted to interact with the PyGTK mainloop.
6
7 In order to use this support, simply do the following::
8
9     |  from twisted.internet import gtkreactor
10     |  gtkreactor.install()
11
12 Then use twisted.internet APIs as usual.  The other methods here are not
13 intended to be called directly.
14 """
15
16 import sys
17
18 # System Imports
19 try:
20     import pygtk
21     pygtk.require('1.2')
22 except ImportError, AttributeError:
23     pass # maybe we're using pygtk before this hack existed.
24 import gtk
25
26 from zope.interface import implements
27
28 # Twisted Imports
29 from twisted.python import log, runtime
30 from twisted.internet.interfaces import IReactorFDSet
31
32 # Sibling Imports
33 from twisted.internet import posixbase, selectreactor
34
35
36 class GtkReactor(posixbase.PosixReactorBase):
37     """
38     GTK+ event loop reactor.
39
40     @ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk INPUT_READ
41         watch handles.
42
43     @ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
44         INTPUT_WRITE watch handles.
45
46     @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
47     """
48     implements(IReactorFDSet)
49
50     def __init__(self):
51         """
52         Initialize the file descriptor tracking dictionaries and the base
53         class.
54         """
55         self._simtag = None
56         self._reads = {}
57         self._writes = {}
58         posixbase.PosixReactorBase.__init__(self)
59
60
61     def addReader(self, reader):
62         if reader not in self._reads:
63             self._reads[reader] = gtk.input_add(reader, gtk.GDK.INPUT_READ, self.callback)
64
65     def addWriter(self, writer):
66         if writer not in self._writes:
67             self._writes[writer] = gtk.input_add(writer, gtk.GDK.INPUT_WRITE, self.callback)
68
69
70     def getReaders(self):
71         return self._reads.keys()
72
73
74     def getWriters(self):
75         return self._writes.keys()
76
77
78     def removeAll(self):
79         return self._removeAll(self._reads, self._writes)
80
81
82     def removeReader(self, reader):
83         if reader in self._reads:
84             gtk.input_remove(self._reads[reader])
85             del self._reads[reader]
86
87     def removeWriter(self, writer):
88         if writer in self._writes:
89             gtk.input_remove(self._writes[writer])
90             del self._writes[writer]
91
92     doIterationTimer = None
93
94     def doIterationTimeout(self, *args):
95         self.doIterationTimer = None
96         return 0 # auto-remove
97     def doIteration(self, delay):
98         # flush some pending events, return if there was something to do
99         # don't use the usual "while gtk.events_pending(): mainiteration()"
100         # idiom because lots of IO (in particular test_tcp's
101         # ProperlyCloseFilesTestCase) can keep us from ever exiting.
102         log.msg(channel='system', event='iteration', reactor=self)
103         if gtk.events_pending():
104             gtk.mainiteration(0)
105             return
106         # nothing to do, must delay
107         if delay == 0:
108             return # shouldn't delay, so just return
109         self.doIterationTimer = gtk.timeout_add(int(delay * 1000),
110                                                 self.doIterationTimeout)
111         # This will either wake up from IO or from a timeout.
112         gtk.mainiteration(1) # block
113         # note: with the .simulate timer below, delays > 0.1 will always be
114         # woken up by the .simulate timer
115         if self.doIterationTimer:
116             # if woken by IO, need to cancel the timer
117             gtk.timeout_remove(self.doIterationTimer)
118             self.doIterationTimer = None
119
120     def crash(self):
121         posixbase.PosixReactorBase.crash(self)
122         gtk.mainquit()
123
124     def run(self, installSignalHandlers=1):
125         self.startRunning(installSignalHandlers=installSignalHandlers)
126         gtk.timeout_add(0, self.simulate)
127         gtk.mainloop()
128
129     def _readAndWrite(self, source, condition):
130         # note: gtk-1.2's gtk_input_add presents an API in terms of gdk
131         # constants like INPUT_READ and INPUT_WRITE. Internally, it will add
132         # POLL_HUP and POLL_ERR to the poll() events, but if they happen it
133         # will turn them back into INPUT_READ and INPUT_WRITE. gdkevents.c
134         # maps IN/HUP/ERR to INPUT_READ, and OUT/ERR to INPUT_WRITE. This
135         # means there is no immediate way to detect a disconnected socket.
136
137         # The g_io_add_watch() API is more suited to this task. I don't think
138         # pygtk exposes it, though.
139         why = None
140         didRead = None
141         try:
142             if condition & gtk.GDK.INPUT_READ:
143                 why = source.doRead()
144                 didRead = source.doRead
145             if not why and condition & gtk.GDK.INPUT_WRITE:
146                 # if doRead caused connectionLost, don't call doWrite
147                 # if doRead is doWrite, don't call it again.
148                 if not source.disconnected and source.doWrite != didRead:
149                     why = source.doWrite()
150                     didRead = source.doWrite # if failed it was in write
151         except:
152             why = sys.exc_info()[1]
153             log.msg('Error In %s' % source)
154             log.deferr()
155
156         if why:
157             self._disconnectSelectable(source, why, didRead == source.doRead)
158
159     def callback(self, source, condition):
160         log.callWithLogger(source, self._readAndWrite, source, condition)
161         self.simulate() # fire Twisted timers
162         return 1 # 1=don't auto-remove the source
163
164     def simulate(self):
165         """Run simulation loops and reschedule callbacks.
166         """
167         if self._simtag is not None:
168             gtk.timeout_remove(self._simtag)
169         self.runUntilCurrent()
170         timeout = min(self.timeout(), 0.1)
171         if timeout is None:
172             timeout = 0.1
173         # Quoth someone other than me, "grumble", yet I know not why. Try to be
174         # more specific in your complaints, guys. -exarkun
175         self._simtag = gtk.timeout_add(int(timeout * 1010), self.simulate)
176
177
178
179 class PortableGtkReactor(selectreactor.SelectReactor):
180     """Reactor that works on Windows.
181
182     input_add is not supported on GTK+ for Win32, apparently.
183
184     @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
185     """
186     _simtag = None
187
188
189     def crash(self):
190         selectreactor.SelectReactor.crash(self)
191         gtk.mainquit()
192
193     def run(self, installSignalHandlers=1):
194         self.startRunning(installSignalHandlers=installSignalHandlers)
195         self.simulate()
196         gtk.mainloop()
197
198     def simulate(self):
199         """Run simulation loops and reschedule callbacks.
200         """
201         if self._simtag is not None:
202             gtk.timeout_remove(self._simtag)
203         self.iterate()
204         timeout = min(self.timeout(), 0.1)
205         if timeout is None:
206             timeout = 0.1
207
208         # See comment for identical line in GtkReactor.simulate.
209         self._simtag = gtk.timeout_add((timeout * 1010), self.simulate)
210
211
212
213 def install():
214     """Configure the twisted mainloop to be run inside the gtk mainloop.
215     """
216     reactor = GtkReactor()
217     from twisted.internet.main import installReactor
218     installReactor(reactor)
219     return reactor
220
221 def portableInstall():
222     """Configure the twisted mainloop to be run inside the gtk mainloop.
223     """
224     reactor = PortableGtkReactor()
225     from twisted.internet.main import installReactor
226     installReactor(reactor)
227     return reactor
228
229 if runtime.platform.getType() != 'posix':
230     install = portableInstall
231
232 __all__ = ['install']
Note: See TracBrowser for help on using the browser.