"""A eventlet based handler."""
from __future__ import absolute_import
import contextlib
import logging
import kazoo.python2atexit as python2atexit
import eventlet
from eventlet.green import select as green_select
from eventlet.green import socket as green_socket
from eventlet.green import time as green_time
from eventlet.green import threading as green_threading
from eventlet import queue as green_queue
from kazoo.handlers import utils
LOG = logging.getLogger(__name__)
# sentinel objects
_STOP = object()
@contextlib.contextmanager
def _yield_before_after():
# Yield to any other co-routines...
#
# See: http://eventlet.net/doc/modules/greenthread.html
# for how this zero sleep is really a cooperative yield to other potential
# co-routines...
eventlet.sleep(0)
try:
yield
finally:
eventlet.sleep(0)
class TimeoutError(Exception):
pass
[docs]class AsyncResult(utils.AsyncResult):
"""A one-time event that stores a value or an exception"""
def __init__(self, handler):
super(AsyncResult, self).__init__(handler,
green_threading.Condition,
TimeoutError)
[docs]class SequentialEventletHandler(object):
"""Eventlet handler for sequentially executing callbacks.
This handler executes callbacks in a sequential manner. A queue is
created for each of the callback events, so that each type of event
has its callback type run sequentially. These are split into two
queues, one for watch events and one for async result completion
callbacks.
Each queue type has a greenthread worker that pulls the callback event
off the queue and runs it in the order the client sees it.
This split helps ensure that watch callbacks won't block session
re-establishment should the connection be lost during a Zookeeper
client call.
Watch and completion callbacks should avoid blocking behavior as
the next callback of that type won't be run until it completes. If
you need to block, spawn a new greenthread and return immediately so
callbacks can proceed.
.. note::
Completion callbacks can block to wait on Zookeeper calls, but
no other completion callbacks will execute until the callback
returns.
"""
name = "sequential_eventlet_handler"
def __init__(self):
"""Create a :class:`SequentialEventletHandler` instance"""
self.callback_queue = green_queue.LightQueue()
self.completion_queue = green_queue.LightQueue()
self._workers = []
self._started = False
@staticmethod
def sleep_func(wait):
green_time.sleep(wait)
@property
def running(self):
return self._started
timeout_exception = TimeoutError
def _process_completion_queue(self):
while True:
cb = self.completion_queue.get()
if cb is _STOP:
break
try:
with _yield_before_after():
cb()
except Exception:
LOG.warning("Exception in worker completion queue greenlet",
exc_info=True)
def _process_callback_queue(self):
while True:
cb = self.callback_queue.get()
if cb is _STOP:
break
try:
with _yield_before_after():
cb()
except Exception:
LOG.warning("Exception in worker callback queue greenlet",
exc_info=True)
def start(self):
if not self._started:
# Spawn our worker threads, we have
# - A callback worker for watch events to be called
# - A completion worker for completion events to be called
w = eventlet.spawn(self._process_completion_queue)
self._workers.append((w, self.completion_queue))
w = eventlet.spawn(self._process_callback_queue)
self._workers.append((w, self.callback_queue))
self._started = True
python2atexit.register(self.stop)
def stop(self):
while self._workers:
w, q = self._workers.pop()
q.put(_STOP)
w.wait()
self._started = False
python2atexit.unregister(self.stop)
def socket(self, *args, **kwargs):
return utils.create_tcp_socket(green_socket)
def create_socket_pair(self):
return utils.create_socket_pair(green_socket)
def event_object(self):
return green_threading.Event()
def lock_object(self):
return green_threading.Lock()
def rlock_object(self):
return green_threading.RLock()
def create_connection(self, *args, **kwargs):
return utils.create_tcp_connection(green_socket, *args, **kwargs)
def select(self, *args, **kwargs):
with _yield_before_after():
return green_select.select(*args, **kwargs)
def async_result(self):
return AsyncResult(self)
def spawn(self, func, *args, **kwargs):
t = green_threading.Thread(target=func, args=args, kwargs=kwargs)
t.daemon = True
t.start()
return t
def dispatch_callback(self, callback):
self.callback_queue.put(lambda: callback.func(*callback.args))