kqreactor.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # -*- test-case-name: twisted.test.test_kqueuereactor -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. A kqueue()/kevent() based implementation of the Twisted main loop.
  6. To use this reactor, start your application specifying the kqueue reactor::
  7. twistd --reactor kqueue ...
  8. To install the event loop from code (and you should do this before any
  9. connections, listeners or connectors are added)::
  10. from twisted.internet import kqreactor
  11. kqreactor.install()
  12. """
  13. import errno
  14. import select
  15. from zope.interface import Attribute, Interface, declarations, implementer
  16. from twisted.internet import main, posixbase
  17. from twisted.internet.interfaces import IReactorDaemonize, IReactorFDSet
  18. from twisted.python import failure, log
  19. try:
  20. # This is to keep mypy from complaining
  21. # We don't use type: ignore[attr-defined] on import, because mypy only complains
  22. # on on some platforms, and then the unused ignore is an issue if the undefined
  23. # attribute isn't.
  24. KQ_EV_ADD = getattr(select, "KQ_EV_ADD")
  25. KQ_EV_DELETE = getattr(select, "KQ_EV_DELETE")
  26. KQ_EV_EOF = getattr(select, "KQ_EV_EOF")
  27. KQ_FILTER_READ = getattr(select, "KQ_FILTER_READ")
  28. KQ_FILTER_WRITE = getattr(select, "KQ_FILTER_WRITE")
  29. except AttributeError as e:
  30. raise ImportError(e)
  31. class _IKQueue(Interface):
  32. """
  33. An interface for KQueue implementations.
  34. """
  35. kqueue = Attribute("An implementation of kqueue(2).")
  36. kevent = Attribute("An implementation of kevent(2).")
  37. declarations.directlyProvides(select, _IKQueue)
  38. @implementer(IReactorFDSet, IReactorDaemonize)
  39. class KQueueReactor(posixbase.PosixReactorBase):
  40. """
  41. A reactor that uses kqueue(2)/kevent(2) and relies on Python 2.6 or higher
  42. which has built in support for kqueue in the select module.
  43. @ivar _kq: A C{kqueue} which will be used to check for I/O readiness.
  44. @ivar _impl: The implementation of L{_IKQueue} to use.
  45. @ivar _selectables: A dictionary mapping integer file descriptors to
  46. instances of L{FileDescriptor} which have been registered with the
  47. reactor. All L{FileDescriptor}s which are currently receiving read or
  48. write readiness notifications will be present as values in this
  49. dictionary.
  50. @ivar _reads: A set containing integer file descriptors. Values in this
  51. set will be registered with C{_kq} for read readiness notifications
  52. which will be dispatched to the corresponding L{FileDescriptor}
  53. instances in C{_selectables}.
  54. @ivar _writes: A set containing integer file descriptors. Values in this
  55. set will be registered with C{_kq} for write readiness notifications
  56. which will be dispatched to the corresponding L{FileDescriptor}
  57. instances in C{_selectables}.
  58. """
  59. def __init__(self, _kqueueImpl=select):
  60. """
  61. Initialize kqueue object, file descriptor tracking dictionaries, and
  62. the base class.
  63. See:
  64. - http://docs.python.org/library/select.html
  65. - www.freebsd.org/cgi/man.cgi?query=kqueue
  66. - people.freebsd.org/~jlemon/papers/kqueue.pdf
  67. @param _kqueueImpl: The implementation of L{_IKQueue} to use. A
  68. hook for testing.
  69. """
  70. self._impl = _kqueueImpl
  71. self._kq = self._impl.kqueue()
  72. self._reads = set()
  73. self._writes = set()
  74. self._selectables = {}
  75. posixbase.PosixReactorBase.__init__(self)
  76. def _updateRegistration(self, fd, filter, op):
  77. """
  78. Private method for changing kqueue registration on a given FD
  79. filtering for events given filter/op. This will never block and
  80. returns nothing.
  81. """
  82. self._kq.control([self._impl.kevent(fd, filter, op)], 0, 0)
  83. def beforeDaemonize(self):
  84. """
  85. Implement L{IReactorDaemonize.beforeDaemonize}.
  86. """
  87. # Twisted-internal method called during daemonization (when application
  88. # is started via twistd). This is called right before the magic double
  89. # forking done for daemonization. We cleanly close the kqueue() and later
  90. # recreate it. This is needed since a) kqueue() are not inherited across
  91. # forks and b) twistd will create the reactor already before daemonization
  92. # (and will also add at least 1 reader to the reactor, an instance of
  93. # twisted.internet.posixbase._UnixWaker).
  94. #
  95. # See: twisted.scripts._twistd_unix.daemonize()
  96. self._kq.close()
  97. self._kq = None
  98. def afterDaemonize(self):
  99. """
  100. Implement L{IReactorDaemonize.afterDaemonize}.
  101. """
  102. # Twisted-internal method called during daemonization. This is called right
  103. # after daemonization and recreates the kqueue() and any readers/writers
  104. # that were added before. Note that you MUST NOT call any reactor methods
  105. # in between beforeDaemonize() and afterDaemonize()!
  106. self._kq = self._impl.kqueue()
  107. for fd in self._reads:
  108. self._updateRegistration(fd, KQ_FILTER_READ, KQ_EV_ADD)
  109. for fd in self._writes:
  110. self._updateRegistration(fd, KQ_FILTER_WRITE, KQ_EV_ADD)
  111. def addReader(self, reader):
  112. """
  113. Implement L{IReactorFDSet.addReader}.
  114. """
  115. fd = reader.fileno()
  116. if fd not in self._reads:
  117. try:
  118. self._updateRegistration(fd, KQ_FILTER_READ, KQ_EV_ADD)
  119. except OSError:
  120. pass
  121. finally:
  122. self._selectables[fd] = reader
  123. self._reads.add(fd)
  124. def addWriter(self, writer):
  125. """
  126. Implement L{IReactorFDSet.addWriter}.
  127. """
  128. fd = writer.fileno()
  129. if fd not in self._writes:
  130. try:
  131. self._updateRegistration(fd, KQ_FILTER_WRITE, KQ_EV_ADD)
  132. except OSError:
  133. pass
  134. finally:
  135. self._selectables[fd] = writer
  136. self._writes.add(fd)
  137. def removeReader(self, reader):
  138. """
  139. Implement L{IReactorFDSet.removeReader}.
  140. """
  141. wasLost = False
  142. try:
  143. fd = reader.fileno()
  144. except BaseException:
  145. fd = -1
  146. if fd == -1:
  147. for fd, fdes in self._selectables.items():
  148. if reader is fdes:
  149. wasLost = True
  150. break
  151. else:
  152. return
  153. if fd in self._reads:
  154. self._reads.remove(fd)
  155. if fd not in self._writes:
  156. del self._selectables[fd]
  157. if not wasLost:
  158. try:
  159. self._updateRegistration(fd, KQ_FILTER_READ, KQ_EV_DELETE)
  160. except OSError:
  161. pass
  162. def removeWriter(self, writer):
  163. """
  164. Implement L{IReactorFDSet.removeWriter}.
  165. """
  166. wasLost = False
  167. try:
  168. fd = writer.fileno()
  169. except BaseException:
  170. fd = -1
  171. if fd == -1:
  172. for fd, fdes in self._selectables.items():
  173. if writer is fdes:
  174. wasLost = True
  175. break
  176. else:
  177. return
  178. if fd in self._writes:
  179. self._writes.remove(fd)
  180. if fd not in self._reads:
  181. del self._selectables[fd]
  182. if not wasLost:
  183. try:
  184. self._updateRegistration(fd, KQ_FILTER_WRITE, KQ_EV_DELETE)
  185. except OSError:
  186. pass
  187. def removeAll(self):
  188. """
  189. Implement L{IReactorFDSet.removeAll}.
  190. """
  191. return self._removeAll(
  192. [self._selectables[fd] for fd in self._reads],
  193. [self._selectables[fd] for fd in self._writes],
  194. )
  195. def getReaders(self):
  196. """
  197. Implement L{IReactorFDSet.getReaders}.
  198. """
  199. return [self._selectables[fd] for fd in self._reads]
  200. def getWriters(self):
  201. """
  202. Implement L{IReactorFDSet.getWriters}.
  203. """
  204. return [self._selectables[fd] for fd in self._writes]
  205. def doKEvent(self, timeout):
  206. """
  207. Poll the kqueue for new events.
  208. """
  209. if timeout is None:
  210. timeout = 1
  211. try:
  212. events = self._kq.control([], len(self._selectables), timeout)
  213. except OSError as e:
  214. # Since this command blocks for potentially a while, it's possible
  215. # EINTR can be raised for various reasons (for example, if the user
  216. # hits ^C).
  217. if e.errno == errno.EINTR:
  218. return
  219. else:
  220. raise
  221. _drdw = self._doWriteOrRead
  222. for event in events:
  223. fd = event.ident
  224. try:
  225. selectable = self._selectables[fd]
  226. except KeyError:
  227. # Handles the infrequent case where one selectable's
  228. # handler disconnects another.
  229. continue
  230. else:
  231. log.callWithLogger(selectable, _drdw, selectable, fd, event)
  232. def _doWriteOrRead(self, selectable, fd, event):
  233. """
  234. Private method called when a FD is ready for reading, writing or was
  235. lost. Do the work and raise errors where necessary.
  236. """
  237. why = None
  238. inRead = False
  239. (filter, flags, data, fflags) = (
  240. event.filter,
  241. event.flags,
  242. event.data,
  243. event.fflags,
  244. )
  245. if flags & KQ_EV_EOF and data and fflags:
  246. why = main.CONNECTION_LOST
  247. else:
  248. try:
  249. if selectable.fileno() == -1:
  250. inRead = False
  251. why = posixbase._NO_FILEDESC
  252. else:
  253. if filter == KQ_FILTER_READ:
  254. inRead = True
  255. why = selectable.doRead()
  256. if filter == KQ_FILTER_WRITE:
  257. inRead = False
  258. why = selectable.doWrite()
  259. except BaseException:
  260. # Any exception from application code gets logged and will
  261. # cause us to disconnect the selectable.
  262. why = failure.Failure()
  263. log.err(
  264. why,
  265. "An exception was raised from application code"
  266. " while processing a reactor selectable",
  267. )
  268. if why:
  269. self._disconnectSelectable(selectable, why, inRead)
  270. doIteration = doKEvent
  271. def install():
  272. """
  273. Install the kqueue() reactor.
  274. """
  275. p = KQueueReactor()
  276. from twisted.internet.main import installReactor
  277. installReactor(p)
  278. __all__ = ["KQueueReactor", "install"]