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.

116 lines
4.3KB

  1. import logging
  2. import pickle
  3. import time
  4. try:
  5. import redis
  6. except ImportError:
  7. redis = None
  8. from .pubsub_manager import PubSubManager
  9. logger = logging.getLogger('socketio')
  10. class RedisManager(PubSubManager): # pragma: no cover
  11. """Redis based client manager.
  12. This class implements a Redis backend for event sharing across multiple
  13. processes. Only kept here as one more example of how to build a custom
  14. backend, since the kombu backend is perfectly adequate to support a Redis
  15. message queue.
  16. To use a Redis backend, initialize the :class:`Server` instance as
  17. follows::
  18. url = 'redis://hostname:port/0'
  19. server = socketio.Server(client_manager=socketio.RedisManager(url))
  20. :param url: The connection URL for the Redis server. For a default Redis
  21. store running on the same host, use ``redis://``.
  22. :param channel: The channel name on which the server sends and receives
  23. notifications. Must be the same in all the servers.
  24. :param write_only: If set ot ``True``, only initialize to emit events. The
  25. default of ``False`` initializes the class for emitting
  26. and receiving.
  27. :param redis_options: additional keyword arguments to be passed to
  28. ``Redis.from_url()``.
  29. """
  30. name = 'redis'
  31. def __init__(self, url='redis://localhost:6379/0', channel='socketio',
  32. write_only=False, logger=None, redis_options=None):
  33. if redis is None:
  34. raise RuntimeError('Redis package is not installed '
  35. '(Run "pip install redis" in your '
  36. 'virtualenv).')
  37. self.redis_url = url
  38. self.redis_options = redis_options or {}
  39. self._redis_connect()
  40. super(RedisManager, self).__init__(channel=channel,
  41. write_only=write_only,
  42. logger=logger)
  43. def initialize(self):
  44. super(RedisManager, self).initialize()
  45. monkey_patched = True
  46. if self.server.async_mode == 'eventlet':
  47. from eventlet.patcher import is_monkey_patched
  48. monkey_patched = is_monkey_patched('socket')
  49. elif 'gevent' in self.server.async_mode:
  50. from gevent.monkey import is_module_patched
  51. monkey_patched = is_module_patched('socket')
  52. if not monkey_patched:
  53. raise RuntimeError(
  54. 'Redis requires a monkey patched socket library to work '
  55. 'with ' + self.server.async_mode)
  56. def _redis_connect(self):
  57. self.redis = redis.Redis.from_url(self.redis_url,
  58. **self.redis_options)
  59. self.pubsub = self.redis.pubsub()
  60. def _publish(self, data):
  61. retry = True
  62. while True:
  63. try:
  64. if not retry:
  65. self._redis_connect()
  66. return self.redis.publish(self.channel, pickle.dumps(data))
  67. except redis.exceptions.ConnectionError:
  68. if retry:
  69. logger.error('Cannot publish to redis... retrying')
  70. retry = False
  71. else:
  72. logger.error('Cannot publish to redis... giving up')
  73. break
  74. def _redis_listen_with_retries(self):
  75. retry_sleep = 1
  76. connect = False
  77. while True:
  78. try:
  79. if connect:
  80. self._redis_connect()
  81. self.pubsub.subscribe(self.channel)
  82. for message in self.pubsub.listen():
  83. yield message
  84. except redis.exceptions.ConnectionError:
  85. logger.error('Cannot receive from redis... '
  86. 'retrying in {} secs'.format(retry_sleep))
  87. connect = True
  88. time.sleep(retry_sleep)
  89. retry_sleep *= 2
  90. if retry_sleep > 60:
  91. retry_sleep = 60
  92. def _listen(self):
  93. channel = self.channel.encode('utf-8')
  94. self.pubsub.subscribe(self.channel)
  95. for message in self._redis_listen_with_retries():
  96. if message['channel'] == channel and \
  97. message['type'] == 'message' and 'data' in message:
  98. yield message['data']
  99. self.pubsub.unsubscribe(self.channel)