|  | @@ -12,6 +12,11 @@
 | 
	
		
			
				|  |  |  # See the License for the specific language governing permissions and
 | 
	
		
			
				|  |  |  # limitations under the License.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +# TODO(https://github.com/grpc/grpc/issues/20850) refactor this.
 | 
	
		
			
				|  |  | +_LOGGER = logging.getLogger(__name__)
 | 
	
		
			
				|  |  | +cdef int _EMPTY_FLAG = 0
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  cdef class _HandlerCallDetails:
 | 
	
		
			
				|  |  |      def __cinit__(self, str method, tuple invocation_metadata):
 | 
	
		
			
				|  |  |          self.method = method
 | 
	
	
		
			
				|  | @@ -21,16 +26,38 @@ cdef class _HandlerCallDetails:
 | 
	
		
			
				|  |  |  class _ServicerContextPlaceHolder(object): pass
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +cdef class _CallbackFailureHandler:
 | 
	
		
			
				|  |  | +    cdef str _core_function_name
 | 
	
		
			
				|  |  | +    cdef object _error_details
 | 
	
		
			
				|  |  | +    cdef object _exception_type
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __cinit__(self,
 | 
	
		
			
				|  |  | +                  str core_function_name,
 | 
	
		
			
				|  |  | +                  object error_details,
 | 
	
		
			
				|  |  | +                  object exception_type):
 | 
	
		
			
				|  |  | +        """Handles failure by raising exception."""
 | 
	
		
			
				|  |  | +        self._core_function_name = core_function_name
 | 
	
		
			
				|  |  | +        self._error_details = error_details
 | 
	
		
			
				|  |  | +        self._exception_type = exception_type
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    cdef handle(self, object future):
 | 
	
		
			
				|  |  | +        future.set_exception(self._exception_type(
 | 
	
		
			
				|  |  | +            'Failed "%s": %s' % (self._core_function_name, self._error_details)
 | 
	
		
			
				|  |  | +        ))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  # TODO(https://github.com/grpc/grpc/issues/20669)
 | 
	
		
			
				|  |  |  # Apply this to the client-side
 | 
	
		
			
				|  |  |  cdef class CallbackWrapper:
 | 
	
		
			
				|  |  | -    cdef CallbackContext context
 | 
	
		
			
				|  |  | -    cdef object _reference
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    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(
 | 
	
	
		
			
				|  | @@ -38,7 +65,8 @@ cdef class CallbackWrapper:
 | 
	
		
			
				|  |  |              int success):
 | 
	
		
			
				|  |  |          cdef CallbackContext *context = <CallbackContext *>functor
 | 
	
		
			
				|  |  |          if success == 0:
 | 
	
		
			
				|  |  | -            (<object>context.waiter).set_exception(RuntimeError())
 | 
	
		
			
				|  |  | +            (<_CallbackFailureHandler>context.failure_handler).handle(
 | 
	
		
			
				|  |  | +                <object>context.waiter)
 | 
	
		
			
				|  |  |          else:
 | 
	
		
			
				|  |  |              (<object>context.waiter).set_result(None)
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -85,7 +113,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, RuntimeError))
 | 
	
		
			
				|  |  |      # 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)
 | 
	
	
		
			
				|  | @@ -142,6 +172,9 @@ async def _handle_unary_unary_rpc(object method_handler,
 | 
	
		
			
				|  |  |      await callback_start_batch(rpc_state, send_ops, loop)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  async def _handle_rpc(list generic_handlers, RPCState rpc_state, object loop):
 | 
	
		
			
				|  |  |      # Finds the method handler (application logic)
 | 
	
		
			
				|  |  |      cdef object method_handler = _find_method_handler(
 | 
	
	
		
			
				|  | @@ -151,6 +184,7 @@ async def _handle_rpc(list generic_handlers, RPCState rpc_state, object loop):
 | 
	
		
			
				|  |  |      if method_handler is None:
 | 
	
		
			
				|  |  |          # TODO(lidiz) return unimplemented error to client side
 | 
	
		
			
				|  |  |          raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      # TODO(lidiz) extend to all 4 types of RPC
 | 
	
		
			
				|  |  |      if method_handler.request_streaming or method_handler.response_streaming:
 | 
	
		
			
				|  |  |          raise NotImplementedError()
 | 
	
	
		
			
				|  | @@ -162,13 +196,21 @@ async def _handle_rpc(list generic_handlers, RPCState rpc_state, object loop):
 | 
	
		
			
				|  |  |          )
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +class _RequestCallError(Exception): pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +cdef _CallbackFailureHandler REQUEST_CALL_FAILURE_HANDLER = _CallbackFailureHandler(
 | 
	
		
			
				|  |  | +    'grpc_server_request_call', 'server shutdown', _RequestCallError)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  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,
 | 
	
		
			
				|  |  | +        REQUEST_CALL_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)
 | 
	
	
		
			
				|  | @@ -186,54 +228,76 @@ async def _server_call_request_call(Server server,
 | 
	
		
			
				|  |  |      return rpc_state
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async def _server_main_loop(Server server,
 | 
	
		
			
				|  |  | -                            _CallbackCompletionQueue cq,
 | 
	
		
			
				|  |  | -                            list generic_handlers):
 | 
	
		
			
				|  |  | -    cdef object loop = asyncio.get_event_loop()
 | 
	
		
			
				|  |  | -    cdef RPCState rpc_state
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    while True:
 | 
	
		
			
				|  |  | -        rpc_state = await _server_call_request_call(
 | 
	
		
			
				|  |  | -            server,
 | 
	
		
			
				|  |  | -            cq,
 | 
	
		
			
				|  |  | -            loop)
 | 
	
		
			
				|  |  | +async def _handle_cancellation_from_core(object rpc_task,
 | 
	
		
			
				|  |  | +                                          RPCState rpc_state,
 | 
	
		
			
				|  |  | +                                          object loop):
 | 
	
		
			
				|  |  | +    cdef ReceiveCloseOnServerOperation op = ReceiveCloseOnServerOperation(_EMPTY_FLAG)
 | 
	
		
			
				|  |  | +    cdef tuple ops = (op,)
 | 
	
		
			
				|  |  | +    await callback_start_batch(rpc_state, ops, loop)
 | 
	
		
			
				|  |  | +    if op.cancelled() and not rpc_task.done():
 | 
	
		
			
				|  |  | +        rpc_task.cancel()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        loop.create_task(_handle_rpc(generic_handlers, rpc_state, loop))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -async def _server_start(Server server,
 | 
	
		
			
				|  |  | -                        _CallbackCompletionQueue cq,
 | 
	
		
			
				|  |  | -                        list generic_handlers):
 | 
	
		
			
				|  |  | -    server.start()
 | 
	
		
			
				|  |  | -    await _server_main_loop(server, cq, generic_handlers)
 | 
	
		
			
				|  |  | +cdef _CallbackFailureHandler CQ_SHUTDOWN_FAILURE_HANDLER = _CallbackFailureHandler(
 | 
	
		
			
				|  |  | +    'grpc_completion_queue_shutdown',
 | 
	
		
			
				|  |  | +    'Unknown',
 | 
	
		
			
				|  |  | +    RuntimeError)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  cdef class _CallbackCompletionQueue:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def __cinit__(self):
 | 
	
		
			
				|  |  | +    def __cinit__(self, object loop):
 | 
	
		
			
				|  |  | +        self._loop = loop
 | 
	
		
			
				|  |  | +        self._shutdown_completed = loop.create_future()
 | 
	
		
			
				|  |  | +        self._wrapper = CallbackWrapper(
 | 
	
		
			
				|  |  | +            self._shutdown_completed,
 | 
	
		
			
				|  |  | +            CQ_SHUTDOWN_FAILURE_HANDLER)
 | 
	
		
			
				|  |  |          self._cq = grpc_completion_queue_create_for_callback(
 | 
	
		
			
				|  |  | -            NULL,
 | 
	
		
			
				|  |  | +            self._wrapper.c_functor(),
 | 
	
		
			
				|  |  |              NULL
 | 
	
		
			
				|  |  |          )
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      cdef grpc_completion_queue* c_ptr(self):
 | 
	
		
			
				|  |  |          return self._cq
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    async def shutdown(self):
 | 
	
		
			
				|  |  | +        grpc_completion_queue_shutdown(self._cq)
 | 
	
		
			
				|  |  | +        await self._shutdown_completed
 | 
	
		
			
				|  |  | +        grpc_completion_queue_destroy(self._cq)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +cdef _CallbackFailureHandler SERVER_SHUTDOWN_FAILURE_HANDLER = _CallbackFailureHandler(
 | 
	
		
			
				|  |  | +    'grpc_server_shutdown_and_notify',
 | 
	
		
			
				|  |  | +    'Unknown',
 | 
	
		
			
				|  |  | +    RuntimeError)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  cdef class AioServer:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def __init__(self, thread_pool, generic_handlers, interceptors, options,
 | 
	
		
			
				|  |  | -                 maximum_concurrent_rpcs, compression):
 | 
	
		
			
				|  |  | +    def __init__(self, loop, thread_pool, generic_handlers, interceptors,
 | 
	
		
			
				|  |  | +                 options, maximum_concurrent_rpcs, compression):
 | 
	
		
			
				|  |  | +        # NOTE(lidiz) Core objects won't be deallocated automatically.
 | 
	
		
			
				|  |  | +        # If AioServer.shutdown is not called, those objects will leak.
 | 
	
		
			
				|  |  |          self._server = Server(options)
 | 
	
		
			
				|  |  | -        self._cq = _CallbackCompletionQueue()
 | 
	
		
			
				|  |  | -        self._status = AIO_SERVER_STATUS_READY
 | 
	
		
			
				|  |  | -        self._generic_handlers = []
 | 
	
		
			
				|  |  | +        self._cq = _CallbackCompletionQueue(loop)
 | 
	
		
			
				|  |  |          grpc_server_register_completion_queue(
 | 
	
		
			
				|  |  |              self._server.c_server,
 | 
	
		
			
				|  |  |              self._cq.c_ptr(),
 | 
	
		
			
				|  |  |              NULL
 | 
	
		
			
				|  |  |          )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._loop = loop
 | 
	
		
			
				|  |  | +        self._status = AIO_SERVER_STATUS_READY
 | 
	
		
			
				|  |  | +        self._generic_handlers = []
 | 
	
		
			
				|  |  |          self.add_generic_rpc_handlers(generic_handlers)
 | 
	
		
			
				|  |  | +        self._serving_task = None
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._shutdown_lock = asyncio.Lock(loop=self._loop)
 | 
	
		
			
				|  |  | +        self._shutdown_completed = self._loop.create_future()
 | 
	
		
			
				|  |  | +        self._shutdown_callback_wrapper = CallbackWrapper(
 | 
	
		
			
				|  |  | +            self._shutdown_completed,
 | 
	
		
			
				|  |  | +            SERVER_SHUTDOWN_FAILURE_HANDLER)
 | 
	
		
			
				|  |  | +        self._crash_exception = None
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          if interceptors:
 | 
	
		
			
				|  |  |              raise NotImplementedError()
 | 
	
	
		
			
				|  | @@ -255,6 +319,46 @@ 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()
 | 
	
		
			
				|  |  | +        cdef RPCState rpc_state
 | 
	
		
			
				|  |  | +        server_started.set_result(True)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        while True:
 | 
	
		
			
				|  |  | +            # When shutdown begins, 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)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            rpc_task = self._loop.create_task(
 | 
	
		
			
				|  |  | +                _handle_rpc(
 | 
	
		
			
				|  |  | +                    self._generic_handlers,
 | 
	
		
			
				|  |  | +                    rpc_state,
 | 
	
		
			
				|  |  | +                    self._loop
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +            self._loop.create_task(
 | 
	
		
			
				|  |  | +                _handle_cancellation_from_core(
 | 
	
		
			
				|  |  | +                    rpc_task,
 | 
	
		
			
				|  |  | +                    rpc_state,
 | 
	
		
			
				|  |  | +                    self._loop
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _serving_task_crash_handler(self, object task):
 | 
	
		
			
				|  |  | +        """Shutdown the server immediately if unexpectedly exited."""
 | 
	
		
			
				|  |  | +        if task.exception() is None:
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        if self._status != AIO_SERVER_STATUS_STOPPING:
 | 
	
		
			
				|  |  | +            self._crash_exception = task.exception()
 | 
	
		
			
				|  |  | +            _LOGGER.exception(self._crash_exception)
 | 
	
		
			
				|  |  | +            self._loop.create_task(self.shutdown(None))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      async def start(self):
 | 
	
		
			
				|  |  |          if self._status == AIO_SERVER_STATUS_RUNNING:
 | 
	
		
			
				|  |  |              return
 | 
	
	
		
			
				|  | @@ -262,14 +366,103 @@ cdef class AioServer:
 | 
	
		
			
				|  |  |              raise RuntimeError('Server not in ready state')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          self._status = AIO_SERVER_STATUS_RUNNING
 | 
	
		
			
				|  |  | -        loop = asyncio.get_event_loop()
 | 
	
		
			
				|  |  | -        loop.create_task(_server_start(
 | 
	
		
			
				|  |  | -            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))
 | 
	
		
			
				|  |  | +        self._serving_task.add_done_callback(self._serving_task_crash_handler)
 | 
	
		
			
				|  |  | +        # 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 _start_shutting_down(self):
 | 
	
		
			
				|  |  | +        """Prepares the server to shutting down.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        This coroutine function is NOT coroutine-safe.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        # The shutdown callback won't be called until there is no live RPC.
 | 
	
		
			
				|  |  | +        grpc_server_shutdown_and_notify(
 | 
	
		
			
				|  |  | +            self._server.c_server,
 | 
	
		
			
				|  |  | +            self._cq._cq,
 | 
	
		
			
				|  |  | +            self._shutdown_callback_wrapper.c_functor())
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Ensures the serving task (coroutine) exits.
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            await self._serving_task
 | 
	
		
			
				|  |  | +        except _RequestCallError:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    async def shutdown(self, grace):
 | 
	
		
			
				|  |  | +        """Gracefully shutdown the C-Core server.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Application should only call shutdown once.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Args:
 | 
	
		
			
				|  |  | +          grace: An optional float indicating the length of grace period in
 | 
	
		
			
				|  |  | +            seconds.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        if self._status == AIO_SERVER_STATUS_READY or self._status == AIO_SERVER_STATUS_STOPPED:
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        async with self._shutdown_lock:
 | 
	
		
			
				|  |  | +            if self._status == AIO_SERVER_STATUS_RUNNING:
 | 
	
		
			
				|  |  | +                self._server.is_shutting_down = True
 | 
	
		
			
				|  |  | +                self._status = AIO_SERVER_STATUS_STOPPING
 | 
	
		
			
				|  |  | +                await self._start_shutting_down()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if grace is None:
 | 
	
		
			
				|  |  | +            # Directly cancels all calls
 | 
	
		
			
				|  |  | +            grpc_server_cancel_all_calls(self._server.c_server)
 | 
	
		
			
				|  |  | +            await self._shutdown_completed
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                await asyncio.wait_for(
 | 
	
		
			
				|  |  | +                    asyncio.shield(
 | 
	
		
			
				|  |  | +                        self._shutdown_completed,
 | 
	
		
			
				|  |  | +                        loop=self._loop
 | 
	
		
			
				|  |  | +                    ),
 | 
	
		
			
				|  |  | +                    grace,
 | 
	
		
			
				|  |  | +                    loop=self._loop,
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            except asyncio.TimeoutError:
 | 
	
		
			
				|  |  | +                # Cancels all ongoing calls by the end of grace period.
 | 
	
		
			
				|  |  | +                grpc_server_cancel_all_calls(self._server.c_server)
 | 
	
		
			
				|  |  | +                await self._shutdown_completed
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        async with self._shutdown_lock:
 | 
	
		
			
				|  |  | +            if self._status == AIO_SERVER_STATUS_STOPPING:
 | 
	
		
			
				|  |  | +                grpc_server_destroy(self._server.c_server)
 | 
	
		
			
				|  |  | +                self._server.c_server = NULL
 | 
	
		
			
				|  |  | +                self._server.is_shutdown = True
 | 
	
		
			
				|  |  | +                self._status = AIO_SERVER_STATUS_STOPPED
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                # Shuts down the completion queue
 | 
	
		
			
				|  |  | +                await self._cq.shutdown()
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    async def wait_for_termination(self, float timeout):
 | 
	
		
			
				|  |  | +        if timeout is None:
 | 
	
		
			
				|  |  | +            await self._shutdown_completed
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                await asyncio.wait_for(
 | 
	
		
			
				|  |  | +                    asyncio.shield(
 | 
	
		
			
				|  |  | +                        self._shutdown_completed,
 | 
	
		
			
				|  |  | +                        loop=self._loop,
 | 
	
		
			
				|  |  | +                    ),
 | 
	
		
			
				|  |  | +                    timeout,
 | 
	
		
			
				|  |  | +                    loop=self._loop,
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            except asyncio.TimeoutError:
 | 
	
		
			
				|  |  | +                if self._crash_exception is not None:
 | 
	
		
			
				|  |  | +                    raise self._crash_exception
 | 
	
		
			
				|  |  | +                return False
 | 
	
		
			
				|  |  | +        if self._crash_exception is not None:
 | 
	
		
			
				|  |  | +            raise self._crash_exception
 | 
	
		
			
				|  |  | +        return True
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __dealloc__(self):
 | 
	
		
			
				|  |  | +        """Deallocation of Core objects are ensured by Python grpc.aio.Server.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    # TODO(https://github.com/grpc/grpc/issues/20668)
 | 
	
		
			
				|  |  | -    # Implement Destruction Methods for AsyncIO Server
 | 
	
		
			
				|  |  | -    def stop(self, unused_grace):
 | 
	
		
			
				|  |  | -        pass
 | 
	
		
			
				|  |  | +        If the Cython representation is deallocated without underlying objects
 | 
	
		
			
				|  |  | +        freed, raise an RuntimeError.
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        if self._status != AIO_SERVER_STATUS_STOPPED:
 | 
	
		
			
				|  |  | +            raise RuntimeError('__dealloc__ called on running server: %d', self._status)
 |