|
|
|
@ -14,6 +14,7 @@ |
|
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cdef class _HandlerCallDetails: |
|
|
|
|
def __cinit__(self, str method, tuple invocation_metadata): |
|
|
|
|
self.method = method |
|
|
|
@ -23,22 +24,60 @@ cdef class _HandlerCallDetails: |
|
|
|
|
class _ServicerContextPlaceHolder(object): pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cdef class CallbackFailureHandler: |
|
|
|
|
cdef str _c_core_api |
|
|
|
|
cdef object _error_details |
|
|
|
|
cdef object _exception_type |
|
|
|
|
cdef object _callback # Callable[[Future], None] |
|
|
|
|
|
|
|
|
|
def __cinit__(self, |
|
|
|
|
str c_core_api="", |
|
|
|
|
object error_details="UNKNOWN", |
|
|
|
|
object exception_type=RuntimeError, |
|
|
|
|
object callback=None): |
|
|
|
|
"""Handles failure by raising exception or execute a callbcak. |
|
|
|
|
|
|
|
|
|
The callback accepts a future, returns nothing. The callback is |
|
|
|
|
expected to finish the future either "set_result" or "set_exception". |
|
|
|
|
""" |
|
|
|
|
if callback is None: |
|
|
|
|
self._c_core_api = c_core_api |
|
|
|
|
self._error_details = error_details |
|
|
|
|
self._exception_type = exception_type |
|
|
|
|
self._callback = self._raise_exception |
|
|
|
|
else: |
|
|
|
|
self._callback = callback |
|
|
|
|
|
|
|
|
|
def _raise_exception(self, object future): |
|
|
|
|
future.set_exception(self._exception_type( |
|
|
|
|
'Failed "%s": %s' % (self._c_core_api, self._error_details) |
|
|
|
|
)) |
|
|
|
|
|
|
|
|
|
cdef handle(self, object future): |
|
|
|
|
self._callback(future) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO(https://github.com/grpc/grpc/issues/20669) |
|
|
|
|
# Apply this to the client-side |
|
|
|
|
cdef class CallbackWrapper: |
|
|
|
|
|
|
|
|
|
def __cinit__(self, object future): |
|
|
|
|
def __cinit__(self, object future, CallbackFailureHandler failure_handler): |
|
|
|
|
self.context.functor.functor_run = self.functor_run |
|
|
|
|
self.context.waiter = <cpython.PyObject*>(future) |
|
|
|
|
self._reference = future |
|
|
|
|
self.context.waiter = <cpython.PyObject*>future |
|
|
|
|
self.context.failure_handler = <cpython.PyObject*>failure_handler |
|
|
|
|
# NOTE(lidiz) Not using a list here, because this class is critical in |
|
|
|
|
# data path. We should make it as efficient as possible. |
|
|
|
|
self._reference_of_future = future |
|
|
|
|
self._reference_of_failure_handler = failure_handler |
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
cdef void functor_run( |
|
|
|
|
grpc_experimental_completion_queue_functor* functor, |
|
|
|
|
int success): |
|
|
|
|
cdef CallbackContext *context = <CallbackContext *>functor |
|
|
|
|
if success == 0: |
|
|
|
|
(<object>context.waiter).set_exception(RuntimeError()) |
|
|
|
|
if succeed == 0: |
|
|
|
|
(<CallbackFailureHandler>context.failure_handler).handle( |
|
|
|
|
<object>context.waiter) |
|
|
|
|
else: |
|
|
|
|
(<object>context.waiter).set_result(None) |
|
|
|
|
|
|
|
|
@ -85,7 +124,9 @@ async def callback_start_batch(RPCState rpc_state, |
|
|
|
|
batch_operation_tag.prepare() |
|
|
|
|
|
|
|
|
|
cdef object future = loop.create_future() |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper(future) |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper( |
|
|
|
|
future, |
|
|
|
|
CallbackFailureHandler('callback_start_batch', operations)) |
|
|
|
|
# NOTE(lidiz) Without Py_INCREF, the wrapper object will be destructed |
|
|
|
|
# when calling "await". This is an over-optimization by Cython. |
|
|
|
|
cpython.Py_INCREF(wrapper) |
|
|
|
@ -162,13 +203,21 @@ async def _handle_rpc(list generic_handlers, RPCState rpc_state, object loop): |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _FINISH_FUTURE(future): |
|
|
|
|
future.set_result(None) |
|
|
|
|
|
|
|
|
|
cdef CallbackFailureHandler IGNORE_FAILURE = CallbackFailureHandler(callback=_FINISH_FUTURE) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _server_call_request_call(Server server, |
|
|
|
|
_CallbackCompletionQueue cq, |
|
|
|
|
object loop): |
|
|
|
|
cdef grpc_call_error error |
|
|
|
|
cdef RPCState rpc_state = RPCState() |
|
|
|
|
cdef object future = loop.create_future() |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper(future) |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper( |
|
|
|
|
future, |
|
|
|
|
IGNORE_FAILURE) |
|
|
|
|
# NOTE(lidiz) Without Py_INCREF, the wrapper object will be destructed |
|
|
|
|
# when calling "await". This is an over-optimization by Cython. |
|
|
|
|
cpython.Py_INCREF(wrapper) |
|
|
|
@ -186,27 +235,7 @@ async def _server_call_request_call(Server server, |
|
|
|
|
return rpc_state |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _server_main_loop(object loop, |
|
|
|
|
Server server, |
|
|
|
|
_CallbackCompletionQueue cq, |
|
|
|
|
list generic_handlers): |
|
|
|
|
cdef RPCState rpc_state |
|
|
|
|
|
|
|
|
|
while True: |
|
|
|
|
rpc_state = await _server_call_request_call( |
|
|
|
|
server, |
|
|
|
|
cq, |
|
|
|
|
loop) |
|
|
|
|
|
|
|
|
|
loop.create_task(_handle_rpc(generic_handlers, rpc_state, loop)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _server_start(object loop, |
|
|
|
|
Server server, |
|
|
|
|
_CallbackCompletionQueue cq, |
|
|
|
|
list generic_handlers): |
|
|
|
|
server.start(backup_queue=False) |
|
|
|
|
await _server_main_loop(loop, server, cq, generic_handlers) |
|
|
|
|
cdef CallbackFailureHandler CQ_SHUTDOWN_FAILURE_HANDLER = CallbackFailureHandler('grpc_completion_queue_shutdown') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cdef class _CallbackCompletionQueue: |
|
|
|
@ -214,7 +243,9 @@ cdef class _CallbackCompletionQueue: |
|
|
|
|
def __cinit__(self, object loop): |
|
|
|
|
self._loop = loop |
|
|
|
|
self._shutdown_completed = loop.create_future() |
|
|
|
|
self._wrapper = CallbackWrapper(self._shutdown_completed) |
|
|
|
|
self._wrapper = CallbackWrapper( |
|
|
|
|
self._shutdown_completed, |
|
|
|
|
CQ_SHUTDOWN_FAILURE_HANDLER) |
|
|
|
|
self._cq = grpc_completion_queue_create_for_callback( |
|
|
|
|
self._wrapper.c_functor(), |
|
|
|
|
NULL |
|
|
|
@ -229,12 +260,13 @@ cdef class _CallbackCompletionQueue: |
|
|
|
|
grpc_completion_queue_destroy(self._cq) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cdef CallbackFailureHandler SERVER_SHUTDOWN_FAILURE_HANDLER = CallbackFailureHandler('grpc_server_shutdown_and_notify') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cdef class AioServer: |
|
|
|
|
|
|
|
|
|
def __init__(self, loop, thread_pool, generic_handlers, interceptors, |
|
|
|
|
options, maximum_concurrent_rpcs, compression): |
|
|
|
|
self._loop = loop |
|
|
|
|
|
|
|
|
|
# C-Core objects won't be deallocated automatically. |
|
|
|
|
self._server = Server(options) |
|
|
|
|
self._cq = _CallbackCompletionQueue(loop) |
|
|
|
@ -244,9 +276,11 @@ cdef class AioServer: |
|
|
|
|
NULL |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
self._loop = loop |
|
|
|
|
self._status = AIO_SERVER_STATUS_READY |
|
|
|
|
self._generic_handlers = [] |
|
|
|
|
self.add_generic_rpc_handlers(generic_handlers) |
|
|
|
|
self._serving_task = None |
|
|
|
|
|
|
|
|
|
if interceptors: |
|
|
|
|
raise NotImplementedError() |
|
|
|
@ -268,6 +302,27 @@ cdef class AioServer: |
|
|
|
|
return self._server.add_http2_port(address, |
|
|
|
|
server_credentials._credentials) |
|
|
|
|
|
|
|
|
|
async def _server_main_loop(self, |
|
|
|
|
object server_started): |
|
|
|
|
self._server.start(backup_queue=False) |
|
|
|
|
server_started.set_result(True) |
|
|
|
|
cdef RPCState rpc_state |
|
|
|
|
|
|
|
|
|
while True: |
|
|
|
|
# When shutdown process starts, no more new connections. |
|
|
|
|
if self._status != AIO_SERVER_STATUS_RUNNING: |
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
rpc_state = await _server_call_request_call( |
|
|
|
|
self._server, |
|
|
|
|
self._cq, |
|
|
|
|
self._loop) |
|
|
|
|
|
|
|
|
|
self._loop.create_task(_handle_rpc( |
|
|
|
|
self._generic_handlers, |
|
|
|
|
rpc_state, |
|
|
|
|
self._loop)) |
|
|
|
|
|
|
|
|
|
async def start(self): |
|
|
|
|
if self._status == AIO_SERVER_STATUS_RUNNING: |
|
|
|
|
return |
|
|
|
@ -275,12 +330,11 @@ cdef class AioServer: |
|
|
|
|
raise RuntimeError('Server not in ready state') |
|
|
|
|
|
|
|
|
|
self._status = AIO_SERVER_STATUS_RUNNING |
|
|
|
|
self._loop.create_task(_server_start( |
|
|
|
|
self._loop, |
|
|
|
|
self._server, |
|
|
|
|
self._cq, |
|
|
|
|
self._generic_handlers, |
|
|
|
|
)) |
|
|
|
|
cdef object server_started = self._loop.create_future() |
|
|
|
|
self._serving_task = self._loop.create_task(self._server_main_loop(server_started)) |
|
|
|
|
# Needs to explicitly wait for the server to start up. |
|
|
|
|
# Otherwise, the actual start time of the server is un-controllable. |
|
|
|
|
await server_started |
|
|
|
|
|
|
|
|
|
async def shutdown(self, grace): |
|
|
|
|
"""Gracefully shutdown the C-Core server. |
|
|
|
@ -295,7 +349,9 @@ cdef class AioServer: |
|
|
|
|
# The server either is shutting down, or not started. |
|
|
|
|
return |
|
|
|
|
cdef object shutdown_completed = self._loop.create_future() |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper(shutdown_completed) |
|
|
|
|
cdef CallbackWrapper wrapper = CallbackWrapper( |
|
|
|
|
shutdown_completed, |
|
|
|
|
SERVER_SHUTDOWN_FAILURE_HANDLER) |
|
|
|
|
# NOTE(lidiz) Without Py_INCREF, the wrapper object will be destructed |
|
|
|
|
# when calling "await". This is an over-optimization by Cython. |
|
|
|
|
cpython.Py_INCREF(wrapper) |
|
|
|
@ -309,6 +365,9 @@ cdef class AioServer: |
|
|
|
|
self._server.is_shutting_down = True |
|
|
|
|
self._status = AIO_SERVER_STATUS_STOPPING |
|
|
|
|
|
|
|
|
|
# Ensures the serving task (coroutine) exits normally |
|
|
|
|
await self._serving_task |
|
|
|
|
|
|
|
|
|
if grace is None: |
|
|
|
|
# Directly cancels all calls |
|
|
|
|
grpc_server_cancel_all_calls(self._server.c_server) |
|
|
|
|