root / trunk / twisted / internet / gtk2reactor.py

Revision 26118, 10.2 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 # -*- test-case-name: twisted.internet.test.test_gtk2reactor -*-
2 # Copyright (c) 2001-2009 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 This module provides support for Twisted to interact with the glib/gtk2
8 mainloop.
9
10 In order to use this support, simply do the following::
11
12     |  from twisted.internet import gtk2reactor
13     |  gtk2reactor.install()
14
15 Then use twisted.internet APIs as usual.  The other methods here are not
16 intended to be called directly.
17
18 When installing the reactor, you can choose whether to use the glib
19 event loop or the GTK+ event loop which is based on it but adds GUI
20 integration.
21 """
22
23 # System Imports
24 import sys
25 from zope.interface import implements
26 try:
27     if not hasattr(sys, 'frozen'):
28         # Don't want to check this for py2exe
29         import pygtk
30         pygtk.require('2.0')
31 except (ImportError, AttributeError):
32     pass # maybe we're using pygtk before this hack existed.
33 import gobject
34 if hasattr(gobject, "threads_init"):
35     # recent versions of python-gtk expose this. python-gtk=2.4.1
36     # (wrapping glib-2.4.7) does. python-gtk=2.0.0 (wrapping
37     # glib-2.2.3) does not.
38     gobject.threads_init()
39
40 # Twisted Imports
41 from twisted.python import log, runtime, failure
42 from twisted.internet.interfaces import IReactorFDSet
43 from twisted.internet import main, posixbase, error, selectreactor
44
45 POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
46
47 # glib's iochannel sources won't tell us about any events that we haven't
48 # asked for, even if those events aren't sensible inputs to the poll()
49 # call.
50 INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
51 OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
52
53 def _our_mainquit():
54     # XXX: gtk.main_quit() (which is used for crash()) raises an exception if
55     # gtk.main_level() == 0; however, all the tests freeze if we use this
56     # function to stop the reactor.  what gives?  (I believe this may have been
57     # a stupid mistake where I forgot to import gtk here... I will remove this
58     # comment if the tests pass)
59     import gtk
60     if gtk.main_level():
61         gtk.main_quit()
62
63 class Gtk2Reactor(posixbase.PosixReactorBase):
64     """
65     GTK+-2 event loop reactor.
66
67     @ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk
68         INPUT_READ watch handles.
69
70     @ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
71         INTPUT_WRITE watch handles.
72
73     @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
74     """
75     implements(IReactorFDSet)
76
77     def __init__(self, useGtk=True):
78         self._simtag = None
79         self._reads = {}
80         self._writes = {}
81         posixbase.PosixReactorBase.__init__(self)
82         # pre 2.3.91 the glib iteration and mainloop functions didn't release
83         # global interpreter lock, thus breaking thread and signal support.
84         if getattr(gobject, "pygtk_version", ()) >= (2, 3, 91) and not useGtk:
85             self.context = gobject.main_context_default()
86             self.__pending = self.context.pending
87             self.__iteration = self.context.iteration
88             self.loop = gobject.MainLoop()
89             self.__crash = self.loop.quit
90             self.__run = self.loop.run
91         else:
92             import gtk
93             self.__pending = gtk.events_pending
94             self.__iteration = gtk.main_iteration
95             self.__crash = _our_mainquit
96             self.__run = gtk.main
97
98     # The input_add function in pygtk1 checks for objects with a
99     # 'fileno' method and, if present, uses the result of that method
100     # as the input source. The pygtk2 input_add does not do this. The
101     # function below replicates the pygtk1 functionality.
102
103     # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
104     # g_io_add_watch() takes different condition bitfields than
105     # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
106     # bug.
107     def input_add(self, source, condition, callback):
108         if hasattr(source, 'fileno'):
109             # handle python objects
110             def wrapper(source, condition, real_s=source, real_cb=callback):
111                 return real_cb(real_s, condition)
112             return gobject.io_add_watch(source.fileno(), condition, wrapper)
113         else:
114             return gobject.io_add_watch(source, condition, callback)
115
116     def addReader(self, reader):
117         if reader not in self._reads:
118             self._reads[reader] = self.input_add(reader, INFLAGS, self.callback)
119
120     def addWriter(self, writer):
121         if writer not in self._writes:
122             self._writes[writer] = self.input_add(writer, OUTFLAGS, self.callback)
123
124
125     def getReaders(self):
126         return self._reads.keys()
127
128
129     def getWriters(self):
130         return self._writes.keys()
131
132
133     def removeAll(self):
134         return self._removeAll(self._reads, self._writes)
135
136
137     def removeReader(self, reader):
138         if reader in self._reads:
139             gobject.source_remove(self._reads[reader])
140             del self._reads[reader]
141
142     def removeWriter(self, writer):
143         if writer in self._writes:
144             gobject.source_remove(self._writes[writer])
145             del self._writes[writer]
146
147     doIterationTimer = None
148
149     def doIterationTimeout(self, *args):
150         self.doIterationTimer = None
151         return 0 # auto-remove
152
153     def doIteration(self, delay):
154         # flush some pending events, return if there was something to do
155         # don't use the usual "while self.context.pending(): self.context.iteration()"
156         # idiom because lots of IO (in particular test_tcp's
157         # ProperlyCloseFilesTestCase) can keep us from ever exiting.
158         log.msg(channel='system', event='iteration', reactor=self)
159         if self.__pending():
160             self.__iteration(0)
161             return
162         # nothing to do, must delay
163         if delay == 0:
164             return # shouldn't delay, so just return
165         self.doIterationTimer = gobject.timeout_add(int(delay * 1000),
166                                                 self.doIterationTimeout)
167         # This will either wake up from IO or from a timeout.
168         self.__iteration(1) # block
169         # note: with the .simulate timer below, delays > 0.1 will always be
170         # woken up by the .simulate timer
171         if self.doIterationTimer:
172             # if woken by IO, need to cancel the timer
173             gobject.source_remove(self.doIterationTimer)
174             self.doIterationTimer = None
175
176     def crash(self):
177         posixbase.PosixReactorBase.crash(self)
178         self.__crash()
179
180     def run(self, installSignalHandlers=1):
181         self.startRunning(installSignalHandlers=installSignalHandlers)
182         gobject.timeout_add(0, self.simulate)
183         if self._started:
184             self.__run()
185
186     def _doReadOrWrite(self, source, condition, faildict={
187         error.ConnectionDone: failure.Failure(error.ConnectionDone()),
188         error.ConnectionLost: failure.Failure(error.ConnectionLost()),
189         }):
190         why = None
191         didRead = None
192         if condition & POLL_DISCONNECTED and \
193                not (condition & gobject.IO_IN):
194             why = main.CONNECTION_LOST
195         else:
196             try:
197                 if condition & gobject.IO_IN:
198                     why = source.doRead()
199                     didRead = source.doRead
200                 if not why and condition & gobject.IO_OUT:
201                     # if doRead caused connectionLost, don't call doWrite
202                     # if doRead is doWrite, don't call it again.
203                     if not source.disconnected and source.doWrite != didRead:
204                         why = source.doWrite()
205                         didRead = source.doWrite # if failed it was in write
206             except:
207                 why = sys.exc_info()[1]
208                 log.msg('Error In %s' % source)
209                 log.deferr()
210
211         if why:
212             self._disconnectSelectable(source, why, didRead == source.doRead)
213
214     def callback(self, source, condition):
215         log.callWithLogger(source, self._doReadOrWrite, source, condition)
216         self.simulate() # fire Twisted timers
217         return 1 # 1=don't auto-remove the source
218
219     def simulate(self):
220         """Run simulation loops and reschedule callbacks.
221         """
222         if self._simtag is not None:
223             gobject.source_remove(self._simtag)
224         self.runUntilCurrent()
225         timeout = min(self.timeout(), 0.1)
226         if timeout is None:
227             timeout = 0.1
228         # grumble
229         self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
230
231
232 class PortableGtkReactor(selectreactor.SelectReactor):
233     """
234     Reactor that works on Windows.
235
236     Sockets aren't supported by GTK+'s input_add on Win32.
237     """
238     _simtag = None
239
240     def crash(self):
241         selectreactor.SelectReactor.crash(self)
242         import gtk
243         # mainquit is deprecated in newer versions
244         if gtk.main_level():
245             if hasattr(gtk, 'main_quit'):
246                 gtk.main_quit()
247             else:
248                 gtk.mainquit()
249
250     def run(self, installSignalHandlers=1):
251         import gtk
252         self.startRunning(installSignalHandlers=installSignalHandlers)
253         gobject.timeout_add(0, self.simulate)
254         # mainloop is deprecated in newer versions
255         if hasattr(gtk, 'main'):
256             gtk.main()
257         else:
258             gtk.mainloop()
259
260     def simulate(self):
261         """Run simulation loops and reschedule callbacks.
262         """
263         if self._simtag is not None:
264             gobject.source_remove(self._simtag)
265         self.iterate()
266         timeout = min(self.timeout(), 0.1)
267         if timeout is None:
268             timeout = 0.1
269         # grumble
270         self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
271
272
273 def install(useGtk=True):
274     """Configure the twisted mainloop to be run inside the gtk mainloop.
275
276     @param useGtk: should glib rather than GTK+ event loop be
277     used (this will be slightly faster but does not support GUI).
278     """
279     reactor = Gtk2Reactor(useGtk)
280     from twisted.internet.main import installReactor
281     installReactor(reactor)
282     return reactor
283
284 def portableInstall(useGtk=True):
285     """Configure the twisted mainloop to be run inside the gtk mainloop.
286     """
287     reactor = PortableGtkReactor()
288     from twisted.internet.main import installReactor
289     installReactor(reactor)
290     return reactor
291
292 if runtime.platform.getType() != 'posix':
293     install = portableInstall
294
295
296 __all__ = ['install']
Note: See TracBrowser for help on using the browser.