Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

zmq.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. """
  2. kombu.transport.zmq
  3. ===================
  4. ZeroMQ transport.
  5. """
  6. from __future__ import absolute_import
  7. import errno
  8. import os
  9. import socket
  10. try:
  11. import zmq
  12. from zmq import ZMQError
  13. except ImportError:
  14. zmq = ZMQError = None # noqa
  15. from kombu.five import Empty
  16. from kombu.log import get_logger
  17. from kombu.serialization import pickle
  18. from kombu.utils import cached_property
  19. from kombu.utils.eventio import poll, READ
  20. from . import virtual
  21. logger = get_logger('kombu.transport.zmq')
  22. DEFAULT_PORT = 5555
  23. DEFAULT_HWM = 128
  24. DEFAULT_INCR = 1
  25. dumps, loads = pickle.dumps, pickle.loads
  26. class MultiChannelPoller(object):
  27. eventflags = READ
  28. def __init__(self):
  29. # active channels
  30. self._channels = set()
  31. # file descriptor -> channel map
  32. self._fd_to_chan = {}
  33. # poll implementation (epoll/kqueue/select)
  34. self.poller = poll()
  35. def close(self):
  36. for fd in self._fd_to_chan:
  37. try:
  38. self.poller.unregister(fd)
  39. except KeyError:
  40. pass
  41. self._channels.clear()
  42. self._fd_to_chan.clear()
  43. self.poller = None
  44. def add(self, channel):
  45. self._channels.add(channel)
  46. def discard(self, channel):
  47. self._channels.discard(channel)
  48. self._fd_to_chan.pop(channel.client.connection.fd, None)
  49. def _register(self, channel):
  50. conn = channel.client.connection
  51. self._fd_to_chan[conn.fd] = channel
  52. self.poller.register(conn.fd, self.eventflags)
  53. def on_poll_start(self):
  54. for channel in self._channels:
  55. self._register(channel)
  56. def on_readable(self, fileno):
  57. chan = self._fd_to_chan[fileno]
  58. return chan.drain_events(), chan
  59. def get(self, timeout=None):
  60. self.on_poll_start()
  61. events = self.poller.poll(timeout)
  62. for fileno, _ in events or []:
  63. return self.on_readable(fileno)
  64. raise Empty()
  65. @property
  66. def fds(self):
  67. return self._fd_to_chan
  68. class Client(object):
  69. def __init__(self, uri='tcp://127.0.0.1', port=DEFAULT_PORT,
  70. hwm=DEFAULT_HWM, swap_size=None, enable_sink=True,
  71. context=None):
  72. try:
  73. scheme, parts = uri.split('://')
  74. except ValueError:
  75. scheme = 'tcp'
  76. parts = uri
  77. endpoints = parts.split(';')
  78. self.port = port
  79. if scheme != 'tcp':
  80. raise NotImplementedError('Currently only TCP can be used')
  81. self.context = context or zmq.Context.instance()
  82. if enable_sink:
  83. self.sink = self.context.socket(zmq.PULL)
  84. self.sink.bind('tcp://*:{0.port}'.format(self))
  85. else:
  86. self.sink = None
  87. self.vent = self.context.socket(zmq.PUSH)
  88. if hasattr(zmq, 'SNDHWM'):
  89. self.vent.setsockopt(zmq.SNDHWM, hwm)
  90. else:
  91. self.vent.setsockopt(zmq.HWM, hwm)
  92. if swap_size:
  93. self.vent.setsockopt(zmq.SWAP, swap_size)
  94. for endpoint in endpoints:
  95. if scheme == 'tcp' and ':' not in endpoint:
  96. endpoint += ':' + str(DEFAULT_PORT)
  97. endpoint = ''.join([scheme, '://', endpoint])
  98. self.connect(endpoint)
  99. def connect(self, endpoint):
  100. self.vent.connect(endpoint)
  101. def get(self, queue=None, timeout=None):
  102. sink = self.sink
  103. try:
  104. if timeout is not None:
  105. prev_timeout, sink.RCVTIMEO = sink.RCVTIMEO, timeout
  106. try:
  107. return sink.recv()
  108. finally:
  109. sink.RCVTIMEO = prev_timeout
  110. else:
  111. return sink.recv()
  112. except ZMQError as exc:
  113. if exc.errno == zmq.EAGAIN:
  114. raise socket.error(errno.EAGAIN, exc.strerror)
  115. else:
  116. raise
  117. def put(self, queue, message, **kwargs):
  118. return self.vent.send(message)
  119. def close(self):
  120. if self.sink and not self.sink.closed:
  121. self.sink.close()
  122. if not self.vent.closed:
  123. self.vent.close()
  124. @property
  125. def connection(self):
  126. if self.sink:
  127. return self.sink
  128. return self.vent
  129. class Channel(virtual.Channel):
  130. Client = Client
  131. hwm = DEFAULT_HWM
  132. swap_size = None
  133. enable_sink = True
  134. port_incr = DEFAULT_INCR
  135. from_transport_options = (
  136. virtual.Channel.from_transport_options +
  137. ('hwm', 'swap_size', 'enable_sink', 'port_incr')
  138. )
  139. def __init__(self, *args, **kwargs):
  140. super_ = super(Channel, self)
  141. super_.__init__(*args, **kwargs)
  142. # Evaluate socket
  143. self.client.connection.closed
  144. self.connection.cycle.add(self)
  145. self.connection_errors = self.connection.connection_errors
  146. def _get(self, queue, timeout=None):
  147. try:
  148. return loads(self.client.get(queue, timeout))
  149. except socket.error as exc:
  150. if exc.errno == errno.EAGAIN and timeout != 0:
  151. raise Empty()
  152. else:
  153. raise
  154. def _put(self, queue, message, **kwargs):
  155. self.client.put(queue, dumps(message, -1), **kwargs)
  156. def _purge(self, queue):
  157. return 0
  158. def _poll(self, cycle, timeout=None):
  159. return cycle.get(timeout=timeout)
  160. def close(self):
  161. if not self.closed:
  162. self.connection.cycle.discard(self)
  163. try:
  164. self.__dict__['client'].close()
  165. except KeyError:
  166. pass
  167. super(Channel, self).close()
  168. def _prepare_port(self, port):
  169. return (port + self.channel_id - 1) * self.port_incr
  170. def _create_client(self):
  171. conninfo = self.connection.client
  172. port = self._prepare_port(conninfo.port or DEFAULT_PORT)
  173. return self.Client(uri=conninfo.hostname or 'tcp://127.0.0.1',
  174. port=port,
  175. hwm=self.hwm,
  176. swap_size=self.swap_size,
  177. enable_sink=self.enable_sink,
  178. context=self.connection.context)
  179. @cached_property
  180. def client(self):
  181. return self._create_client()
  182. class Transport(virtual.Transport):
  183. Channel = Channel
  184. can_parse_url = True
  185. default_port = DEFAULT_PORT
  186. driver_type = 'zeromq'
  187. driver_name = 'zmq'
  188. connection_errors = virtual.Transport.connection_errors + (ZMQError, )
  189. supports_ev = True
  190. polling_interval = None
  191. def __init__(self, *args, **kwargs):
  192. if zmq is None:
  193. raise ImportError('The zmq library is not installed')
  194. super(Transport, self).__init__(*args, **kwargs)
  195. self.cycle = MultiChannelPoller()
  196. def driver_version(self):
  197. return zmq.__version__
  198. def register_with_event_loop(self, connection, loop):
  199. cycle = self.cycle
  200. cycle.poller = loop.poller
  201. add_reader = loop.add_reader
  202. on_readable = self.on_readable
  203. cycle_poll_start = cycle.on_poll_start
  204. def on_poll_start():
  205. cycle_poll_start()
  206. [add_reader(fd, on_readable, fd) for fd in cycle.fds]
  207. loop.on_tick.add(on_poll_start)
  208. def on_readable(self, fileno):
  209. self._handle_event(self.cycle.on_readable(fileno))
  210. def drain_events(self, connection, timeout=None):
  211. more_to_read = False
  212. for channel in connection.channels:
  213. try:
  214. evt = channel.cycle.get(timeout=timeout)
  215. except socket.error as exc:
  216. if exc.errno == errno.EAGAIN:
  217. continue
  218. raise
  219. else:
  220. connection._handle_event((evt, channel))
  221. more_to_read = True
  222. if not more_to_read:
  223. raise socket.error(errno.EAGAIN, os.strerror(errno.EAGAIN))
  224. def _handle_event(self, evt):
  225. item, channel = evt
  226. message, queue = item
  227. if not queue or queue not in self._callbacks:
  228. raise KeyError(
  229. 'Message for queue {0!r} without consumers: {1}'.format(
  230. queue, message))
  231. self._callbacks[queue](message)
  232. def establish_connection(self):
  233. self.context.closed
  234. return super(Transport, self).establish_connection()
  235. def close_connection(self, connection):
  236. super(Transport, self).close_connection(connection)
  237. try:
  238. connection.__dict__['context'].term()
  239. except KeyError:
  240. pass
  241. @cached_property
  242. def context(self):
  243. return zmq.Context(1)