_threadedselect.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. # -*- test-case-name: twisted.test.test_internet -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Threaded select reactor
  6. The threadedselectreactor is a specialized reactor for integrating with
  7. arbitrary foreign event loop, such as those you find in GUI toolkits.
  8. There are three things you'll need to do to use this reactor.
  9. Install the reactor at the beginning of your program, before importing
  10. the rest of Twisted::
  11. | from twisted.internet import _threadedselect
  12. | _threadedselect.install()
  13. Interleave this reactor with your foreign event loop, at some point after
  14. your event loop is initialized::
  15. | from twisted.internet import reactor
  16. | reactor.interleave(foreignEventLoopWakerFunction)
  17. | self.addSystemEventTrigger('after', 'shutdown', foreignEventLoopStop)
  18. Instead of shutting down the foreign event loop directly, shut down the
  19. reactor::
  20. | from twisted.internet import reactor
  21. | reactor.stop()
  22. In order for Twisted to do its work in the main thread (the thread that
  23. interleave is called from), a waker function is necessary. The waker function
  24. will be called from a "background" thread with one argument: func.
  25. The waker function's purpose is to call func() from the main thread.
  26. Many GUI toolkits ship with appropriate waker functions.
  27. Some examples of this are wxPython's wx.callAfter (may be wxCallAfter in
  28. older versions of wxPython) or PyObjC's PyObjCTools.AppHelper.callAfter.
  29. These would be used in place of "foreignEventLoopWakerFunction" in the above
  30. example.
  31. The other integration point at which the foreign event loop and this reactor
  32. must integrate is shutdown. In order to ensure clean shutdown of Twisted,
  33. you must allow for Twisted to come to a complete stop before quitting the
  34. application. Typically, you will do this by setting up an after shutdown
  35. trigger to stop your foreign event loop, and call reactor.stop() where you
  36. would normally have initiated the shutdown procedure for the foreign event
  37. loop. Shutdown functions that could be used in place of
  38. "foreignEventloopStop" would be the ExitMainLoop method of the wxApp instance
  39. with wxPython, or the PyObjCTools.AppHelper.stopEventLoop function.
  40. """
  41. from functools import partial
  42. from threading import Thread
  43. try:
  44. from queue import Queue, Empty
  45. except ImportError:
  46. from Queue import Queue, Empty
  47. import sys
  48. from zope.interface import implementer
  49. from twisted.internet.interfaces import IReactorFDSet
  50. from twisted.internet import posixbase
  51. from twisted.internet.posixbase import _NO_FILENO, _NO_FILEDESC
  52. from twisted.python import log, failure, threadable
  53. import select
  54. from errno import EINTR, EBADF
  55. from twisted.internet.selectreactor import _select
  56. def dictRemove(dct, value):
  57. try:
  58. del dct[value]
  59. except KeyError:
  60. pass
  61. def raiseException(e):
  62. raise e
  63. @implementer(IReactorFDSet)
  64. class ThreadedSelectReactor(posixbase.PosixReactorBase):
  65. """A threaded select() based reactor - runs on all POSIX platforms and on
  66. Win32.
  67. """
  68. def __init__(self):
  69. threadable.init(1)
  70. self.reads = {}
  71. self.writes = {}
  72. self.toThreadQueue = Queue()
  73. self.toMainThread = Queue()
  74. self.workerThread = None
  75. self.mainWaker = None
  76. posixbase.PosixReactorBase.__init__(self)
  77. self.addSystemEventTrigger('after', 'shutdown', self._mainLoopShutdown)
  78. def wakeUp(self):
  79. # we want to wake up from any thread
  80. self.waker.wakeUp()
  81. def callLater(self, *args, **kw):
  82. tple = posixbase.PosixReactorBase.callLater(self, *args, **kw)
  83. self.wakeUp()
  84. return tple
  85. def _sendToMain(self, msg, *args):
  86. self.toMainThread.put((msg, args))
  87. if self.mainWaker is not None:
  88. self.mainWaker()
  89. def _sendToThread(self, fn, *args):
  90. self.toThreadQueue.put((fn, args))
  91. def _preenDescriptorsInThread(self):
  92. log.msg("Malformed file descriptor found. Preening lists.")
  93. readers = self.reads.keys()
  94. writers = self.writes.keys()
  95. self.reads.clear()
  96. self.writes.clear()
  97. for selDict, selList in ((self.reads, readers), (self.writes, writers)):
  98. for selectable in selList:
  99. try:
  100. select.select([selectable], [selectable], [selectable], 0)
  101. except:
  102. log.msg("bad descriptor %s" % selectable)
  103. else:
  104. selDict[selectable] = 1
  105. def _workerInThread(self):
  106. try:
  107. while 1:
  108. fn, args = self.toThreadQueue.get()
  109. fn(*args)
  110. except SystemExit:
  111. pass # Exception indicates this thread should exit
  112. except:
  113. f = failure.Failure()
  114. self._sendToMain('Failure', f)
  115. def _doSelectInThread(self, timeout):
  116. """Run one iteration of the I/O monitor loop.
  117. This will run all selectables who had input or output readiness
  118. waiting for them.
  119. """
  120. reads = self.reads
  121. writes = self.writes
  122. while 1:
  123. try:
  124. r, w, ignored = _select(reads.keys(),
  125. writes.keys(),
  126. [], timeout)
  127. break
  128. except ValueError:
  129. # Possibly a file descriptor has gone negative?
  130. log.err()
  131. self._preenDescriptorsInThread()
  132. except TypeError:
  133. # Something *totally* invalid (object w/o fileno, non-integral
  134. # result) was passed
  135. log.err()
  136. self._preenDescriptorsInThread()
  137. except (select.error, IOError) as se:
  138. # select(2) encountered an error
  139. if se.args[0] in (0, 2):
  140. # windows does this if it got an empty list
  141. if (not reads) and (not writes):
  142. return
  143. else:
  144. raise
  145. elif se.args[0] == EINTR:
  146. return
  147. elif se.args[0] == EBADF:
  148. self._preenDescriptorsInThread()
  149. else:
  150. # OK, I really don't know what's going on. Blow up.
  151. raise
  152. self._sendToMain('Notify', r, w)
  153. def _process_Notify(self, r, w):
  154. reads = self.reads
  155. writes = self.writes
  156. _drdw = self._doReadOrWrite
  157. _logrun = log.callWithLogger
  158. for selectables, method, dct in (
  159. (r, "doRead", reads), (w, "doWrite", writes)):
  160. for selectable in selectables:
  161. # if this was disconnected in another thread, kill it.
  162. if selectable not in dct:
  163. continue
  164. # This for pausing input when we're not ready for more.
  165. _logrun(selectable, _drdw, selectable, method, dct)
  166. def _process_Failure(self, f):
  167. f.raiseException()
  168. _doIterationInThread = _doSelectInThread
  169. def ensureWorkerThread(self):
  170. if self.workerThread is None or not self.workerThread.isAlive():
  171. self.workerThread = Thread(target=self._workerInThread)
  172. self.workerThread.start()
  173. def doThreadIteration(self, timeout):
  174. self._sendToThread(self._doIterationInThread, timeout)
  175. self.ensureWorkerThread()
  176. msg, args = self.toMainThread.get()
  177. getattr(self, '_process_' + msg)(*args)
  178. doIteration = doThreadIteration
  179. def _interleave(self):
  180. while self.running:
  181. self.runUntilCurrent()
  182. t2 = self.timeout()
  183. t = self.running and t2
  184. self._sendToThread(self._doIterationInThread, t)
  185. yield None
  186. msg, args = self.toMainThread.get_nowait()
  187. getattr(self, '_process_' + msg)(*args)
  188. def interleave(self, waker, *args, **kw):
  189. """
  190. interleave(waker) interleaves this reactor with the
  191. current application by moving the blocking parts of
  192. the reactor (select() in this case) to a separate
  193. thread. This is typically useful for integration with
  194. GUI applications which have their own event loop
  195. already running.
  196. See the module docstring for more information.
  197. """
  198. self.startRunning(*args, **kw)
  199. loop = self._interleave()
  200. def mainWaker(waker=waker, loop=loop):
  201. waker(partial(next, loop))
  202. self.mainWaker = mainWaker
  203. next(loop)
  204. self.ensureWorkerThread()
  205. def _mainLoopShutdown(self):
  206. self.mainWaker = None
  207. if self.workerThread is not None:
  208. self._sendToThread(raiseException, SystemExit)
  209. self.wakeUp()
  210. try:
  211. while 1:
  212. msg, args = self.toMainThread.get_nowait()
  213. except Empty:
  214. pass
  215. self.workerThread.join()
  216. self.workerThread = None
  217. try:
  218. while 1:
  219. fn, args = self.toThreadQueue.get_nowait()
  220. if fn is self._doIterationInThread:
  221. log.msg('Iteration is still in the thread queue!')
  222. elif fn is raiseException and args[0] is SystemExit:
  223. pass
  224. else:
  225. fn(*args)
  226. except Empty:
  227. pass
  228. def _doReadOrWrite(self, selectable, method, dict):
  229. try:
  230. why = getattr(selectable, method)()
  231. handfn = getattr(selectable, 'fileno', None)
  232. if not handfn:
  233. why = _NO_FILENO
  234. elif handfn() == -1:
  235. why = _NO_FILEDESC
  236. except:
  237. why = sys.exc_info()[1]
  238. log.err()
  239. if why:
  240. self._disconnectSelectable(selectable, why, method == "doRead")
  241. def addReader(self, reader):
  242. """Add a FileDescriptor for notification of data available to read.
  243. """
  244. self._sendToThread(self.reads.__setitem__, reader, 1)
  245. self.wakeUp()
  246. def addWriter(self, writer):
  247. """Add a FileDescriptor for notification of data available to write.
  248. """
  249. self._sendToThread(self.writes.__setitem__, writer, 1)
  250. self.wakeUp()
  251. def removeReader(self, reader):
  252. """Remove a Selectable for notification of data available to read.
  253. """
  254. self._sendToThread(dictRemove, self.reads, reader)
  255. def removeWriter(self, writer):
  256. """Remove a Selectable for notification of data available to write.
  257. """
  258. self._sendToThread(dictRemove, self.writes, writer)
  259. def removeAll(self):
  260. return self._removeAll(self.reads, self.writes)
  261. def getReaders(self):
  262. return list(self.reads.keys())
  263. def getWriters(self):
  264. return list(self.writes.keys())
  265. def stop(self):
  266. """
  267. Extend the base stop implementation to also wake up the select thread so
  268. that C{runUntilCurrent} notices the reactor should stop.
  269. """
  270. posixbase.PosixReactorBase.stop(self)
  271. self.wakeUp()
  272. def run(self, installSignalHandlers=True):
  273. self.startRunning(installSignalHandlers=installSignalHandlers)
  274. self.mainLoop()
  275. def mainLoop(self):
  276. q = Queue()
  277. self.interleave(q.put)
  278. while self.running:
  279. try:
  280. q.get()()
  281. except StopIteration:
  282. break
  283. def install():
  284. """Configure the twisted mainloop to be run using the select() reactor.
  285. """
  286. reactor = ThreadedSelectReactor()
  287. from twisted.internet.main import installReactor
  288. installReactor(reactor)
  289. return reactor
  290. __all__ = ['install']