1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
6
This module provides support for Twisted to interact with CoreFoundation
7
CFRunLoops. This includes Cocoa's NSRunLoop.
9
In order to use this support, simply do the following::
11
| from twisted.internet import cfreactor
14
Then use the twisted.internet APIs as usual. The other methods here are not
15
intended to be called directly under normal use. However, install can take
16
a runLoop kwarg, and run will take a withRunLoop arg if you need to explicitly
17
pass a CFRunLoop for some reason. Otherwise it will make a pretty good guess
18
as to which runLoop you want (the current NSRunLoop if PyObjC is imported,
19
otherwise the current CFRunLoop. Either way, if one doesn't exist, it will
22
Maintainer: Bob Ippolito
33
import cfsupport as cf
35
from zope.interface import implements
37
from twisted.python import log, threadable, failure
38
from twisted.internet.interfaces import IReactorFDSet
39
from twisted.internet import posixbase, error
40
from weakref import WeakKeyDictionary
41
from Foundation import NSRunLoop
42
from AppKit import NSApp
44
# cache two extremely common "failures" without traceback info
46
error.ConnectionDone: failure.Failure(error.ConnectionDone()),
47
error.ConnectionLost: failure.Failure(error.ConnectionLost()),
50
class SelectableSocketWrapper(object):
51
_objCache = WeakKeyDictionary()
54
def socketWrapperForReactorAndObject(klass, reactor, obj):
55
_objCache = klass._objCache
58
v = _objCache[obj] = klass(reactor, obj)
60
socketWrapperForReactorAndObject = classmethod(socketWrapperForReactorAndObject)
62
def __init__(self, reactor, obj):
64
raise ValueError, "This socket wrapper is already initialized"
65
self.reactor = reactor
67
obj._orig_ssw_connectionLost = obj.connectionLost
68
obj.connectionLost = self.objConnectionLost
69
self.fd = obj.fileno()
72
self.wouldRead = False
73
self.wouldWrite = False
74
self.cf = cf.PyCFSocket(obj.fileno(), self.doRead, self.doWrite, self.doConnect)
76
reactor.getRunLoop().addSocket(self.cf)
79
return 'SSW(fd=%r r=%r w=%r x=%08x o=%08x)' % (self.fd, int(self.reading), int(self.writing), id(self), id(self.obj))
81
def objConnectionLost(self, *args, **kwargs):
83
self.reactor.removeReader(obj)
84
self.reactor.removeWriter(obj)
85
obj.connectionLost = obj._orig_ssw_connectionLost
86
obj.connectionLost(*args, **kwargs)
88
del self._objCache[obj]
94
def doConnect(self, why):
97
def startReading(self):
98
self.cf.startReading()
101
if not self.reactor.running:
102
self.reactor.callLater(0, self.doRead)
105
self.wouldRead = False
108
def stopReading(self):
109
self.cf.stopReading()
111
self.wouldRead = False
114
def startWriting(self):
115
self.cf.startWriting()
118
if not self.reactor.running:
119
self.reactor.callLater(0, self.doWrite)
122
self.wouldWrite = False
125
def stopWriting(self):
126
self.cf.stopWriting()
128
self.wouldWrite = False
130
def _finishReadOrWrite(self, fn, faildict=_faildict):
134
why = sys.exc_info()[1]
138
f = faildict.get(why.__class__) or failure.Failure(why)
139
self.objConnectionLost(f)
142
if self.reactor.running:
143
self.reactor.simulate()
150
self.wouldRead = True
151
if self.reactor.running:
152
self.reactor.simulate()
154
self._finishReadOrWrite(obj.doRead)
161
self.wouldWrite = True
162
if self.reactor.running:
163
self.reactor.simulate()
165
self._finishReadOrWrite(obj.doWrite)
170
class CFReactor(posixbase.PosixReactorBase):
171
implements(IReactorFDSet)
172
# how long to poll if we're don't care about signals
173
longIntervalOfTime = 60.0
175
# how long we should poll if we do care about signals
176
shortIntervalOfTime = 1.0
179
pollInterval = longIntervalOfTime
181
def __init__(self, runLoop=None):
185
self.crashing = False
186
self._doRunUntilCurrent = True
189
self.nsRunLoop = None
190
self.didStartRunLoop = False
191
if runLoop is not None:
192
self.getRunLoop(runLoop)
193
posixbase.PosixReactorBase.__init__(self)
195
def getRunLoop(self, runLoop=None):
196
if self.runLoop is None:
197
self.nsRunLoop = runLoop or NSRunLoop.currentRunLoop()
198
self.runLoop = cf.PyCFRunLoop(self.nsRunLoop.getCFRunLoop())
201
def addReader(self, reader):
202
self.readers[reader] = SelectableSocketWrapper.socketWrapperForReactorAndObject(self, reader).startReading()
204
def addWriter(self, writer):
205
self.writers[writer] = SelectableSocketWrapper.socketWrapperForReactorAndObject(self, writer).startWriting()
207
def removeReader(self, reader):
208
wrapped = self.readers.get(reader, None)
209
if wrapped is not None:
210
del self.readers[reader]
211
wrapped.stopReading()
213
def removeWriter(self, writer):
214
wrapped = self.writers.get(writer, None)
215
if wrapped is not None:
216
del self.writers[writer]
217
wrapped.stopWriting()
220
def getReaders(self):
221
return self.readers.keys()
224
def getWriters(self):
225
return self.writers.keys()
229
r = self.readers.keys()
230
for s in self.readers.itervalues():
232
for s in self.writers.itervalues():
238
def run(self, installSignalHandlers=1, withRunLoop=None):
240
raise ValueError, "Reactor already running"
241
if installSignalHandlers:
242
self.pollInterval = self.shortIntervalOfTime
243
runLoop = self.getRunLoop(withRunLoop)
246
self.startRunning(installSignalHandlers=installSignalHandlers)
249
if NSApp() is None and self.nsRunLoop.currentMode() is None:
250
# Most of the time the NSRunLoop will have already started,
251
# but in this case it wasn't.
253
self.crashing = False
254
self.didStartRunLoop = True
256
def callLater(self, howlong, *args, **kwargs):
257
rval = posixbase.PosixReactorBase.callLater(self, howlong, *args, **kwargs)
259
timeout = self.timeout()
262
sleepUntil = cf.now() + min(timeout, howlong)
263
if sleepUntil < self.timer.getNextFireDate():
264
self.timer.setNextFireDate(sleepUntil)
269
def iterate(self, howlong=0.0):
271
raise ValueError, "Can't iterate a running reactor"
272
self.runUntilCurrent()
273
self.doIteration(howlong)
275
def doIteration(self, howlong):
277
raise ValueError, "Can't iterate a running reactor"
278
howlong = howlong or 0.01
279
pi = self.pollInterval
280
self.pollInterval = howlong
281
self._doRunUntilCurrent = False
283
self._doRunUntilCurrent = True
284
self.pollInterval = pi
290
raise ValueError, "You can't simulate a stopped reactor"
291
if self._doRunUntilCurrent:
292
self.runUntilCurrent()
295
if self.timer is None:
299
nap = self.pollInterval
301
nap = min(self.pollInterval, nap)
303
self.timer.setNextFireDate(cf.now() + nap)
304
if not self._doRunUntilCurrent:
309
raise ValueError, "Can't bootstrap a running reactor"
310
self.timer = cf.PyCFRunLoopTimer(cf.now(), self.pollInterval, self.simulate)
311
self.runLoop.addTimer(self.timer)
316
def sigInt(self, *args):
317
self.callLater(0.0, self.stop)
321
raise ValueError, "Can't crash a stopped reactor"
322
posixbase.PosixReactorBase.crash(self)
324
if self.timer is not None:
325
self.runLoop.removeTimer(self.timer)
327
if self.didStartRunLoop:
332
raise ValueError, "Can't stop a stopped reactor"
333
posixbase.PosixReactorBase.stop(self)
335
def install(runLoop=None):
336
"""Configure the twisted mainloop to be run inside CFRunLoop.
338
reactor = CFReactor(runLoop=runLoop)
339
reactor.addSystemEventTrigger('after', 'shutdown', reactor.cleanup)
340
from twisted.internet.main import installReactor
341
installReactor(reactor)