From e00f8b34922f7c589223f46a24b568ea314c4bb9 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 12:05:05 -0800 Subject: [PATCH 01/14] Use a poller thread to replace custom IO manager --- .../grpc/_cython/_cygrpc/aio/channel.pxd.pxi | 3 +- .../grpc/_cython/_cygrpc/aio/channel.pyx.pxi | 3 +- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 25 +- .../_cython/_cygrpc/aio/iomgr/socket.pyx.pxi | 3 + .../grpc/_cython/_cygrpc/aio/poller.pxd.pxi | 11 + .../grpc/_cython/_cygrpc/aio/poller.pyx.pxi | 89 +++ .../grpc/_cython/_cygrpc/aio/server.pxd.pxi | 3 +- .../grpc/_cython/_cygrpc/aio/server.pyx.pxi | 3 +- src/python/grpcio/grpc/_cython/cygrpc.pxd | 1 + src/python/grpcio/grpc/_cython/cygrpc.pyx | 1 + .../grpcio_tests/tests_aio/unit/call_test.py | 564 +++++++++--------- 11 files changed, 417 insertions(+), 289 deletions(-) create mode 100644 src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi create mode 100644 src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi index 50879b4a224..62215b2d24f 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi @@ -21,7 +21,8 @@ cdef enum AioChannelStatus: cdef class AioChannel: cdef: grpc_channel * channel - CallbackCompletionQueue cq + # CallbackCompletionQueue cq + BackgroundCompletionQueue cq object loop bytes _target AioChannelStatus _status diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi index bfa9477b6d1..cc6c5d99306 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi @@ -31,7 +31,8 @@ cdef class AioChannel: options = () cdef _ChannelArgs channel_args = _ChannelArgs(options) self._target = target - self.cq = CallbackCompletionQueue() + # self.cq = CallbackCompletionQueue() + self.cq = BackgroundCompletionQueue() self.loop = loop self._status = AIO_CHANNEL_STATUS_READY diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index da3f976f117..95c6caf2501 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -19,22 +19,25 @@ cdef bint _grpc_aio_initialized = False # we should support this use case. So, the gRPC Python Async Stack should use # a single event loop picked by "init_grpc_aio". cdef object _grpc_aio_loop +cdef object _event_loop_thread_ident def init_grpc_aio(): global _grpc_aio_initialized global _grpc_aio_loop + global _event_loop_thread_ident if _grpc_aio_initialized: return else: _grpc_aio_initialized = True + _event_loop_thread_ident = threading.current_thread().ident # Anchors the event loop that the gRPC library going to use. _grpc_aio_loop = asyncio.get_event_loop() # Activates asyncio IO manager - install_asyncio_iomgr() + # install_asyncio_iomgr() # TODO(https://github.com/grpc/grpc/issues/22244) we need a the # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC @@ -44,11 +47,11 @@ def init_grpc_aio(): # Timers are triggered by the Asyncio loop. We disable # the background thread that is being used by the native # gRPC iomgr. - grpc_timer_manager_set_threading(False) + # grpc_timer_manager_set_threading(False) # gRPC callbaks are executed within the same thread used by the Asyncio # event loop, as it is being done by the other Asyncio callbacks. - Executor.SetThreadingAll(False) + # Executor.SetThreadingAll(False) _grpc_aio_initialized = False @@ -56,3 +59,19 @@ def init_grpc_aio(): def grpc_aio_loop(): """Returns the one-and-only gRPC Aio event loop.""" return _grpc_aio_loop + + +cdef grpc_schedule_coroutine(object coro): + """Thread-safely schedules coroutine to gRPC Aio event loop. + + If invoked within the same thread as the event loop, return an + Asyncio.Task. Otherwise, return a concurrent.futures.Future (the sync + Future). For non-asyncio threads, sync Future objects are probably easier + to handle (without worrying other thread-safety stuff). + """ + assert _event_loop_thread_ident != threading.current_thread().ident + return asyncio.run_coroutine_threadsafe(coro, _grpc_aio_loop) + + +def grpc_call_soon_threadsafe(object func, *args): + return _grpc_aio_loop.call_soon_threadsafe(func, *args) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi index 65ee6e24e59..11b3653e0bf 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi @@ -159,6 +159,7 @@ cdef class _AsyncioSocket: return self._reader and not self._reader._transport.is_closing() cdef void close(self): + _LOGGER.debug('closed!') if self.is_connected(): self._writer.close() if self._server: @@ -196,7 +197,9 @@ cdef class _AsyncioSocket: self._new_connection_callback, sock=self._py_socket, ) + _LOGGER.debug('start listen') + _LOGGER.debug('want to listen') grpc_aio_loop().create_task(create_asyncio_server()) cdef accept(self, diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi new file mode 100644 index 00000000000..c66d24bd502 --- /dev/null +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi @@ -0,0 +1,11 @@ +cdef gpr_timespec _GPR_INF_FUTURE = gpr_inf_future(GPR_CLOCK_REALTIME) + +cdef class BackgroundCompletionQueue: + cdef grpc_completion_queue *_cq + cdef bint _shutdown + cdef object _shutdown_completed + cdef object _poller + cdef object _poller_running + + cdef _polling(self) + cdef grpc_completion_queue* c_ptr(self) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi new file mode 100644 index 00000000000..71d429139a4 --- /dev/null +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi @@ -0,0 +1,89 @@ +# Copyright 2020 The gRPC Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cdef gpr_timespec _GPR_INF_FUTURE = gpr_inf_future(GPR_CLOCK_REALTIME) + + +def _handle_callback_wrapper(CallbackWrapper callback_wrapper, int success): + try: + CallbackWrapper.functor_run(callback_wrapper.c_functor(), success) + _LOGGER.debug('_handle_callback_wrapper Done') + except Exception as e: + _LOGGER.debug('_handle_callback_wrapper EXP') + _LOGGER.exception(e) + raise + + +cdef class BackgroundCompletionQueue: + + def __cinit__(self): + self._cq = grpc_completion_queue_create_for_next(NULL) + self._shutdown = False + self._shutdown_completed = asyncio.get_event_loop().create_future() + self._poller = None + self._poller_running = asyncio.get_event_loop().create_future() + # asyncio.get_event_loop().create_task(self._start_poller()) + self._poller = threading.Thread(target=self._polling_wrapper) + self._poller.daemon = True + self._poller.start() + + # async def _start_poller(self): + # if self._poller: + # raise UsageError('Poller can only be started once.') + + # self._poller = threading.Thread(target=self._polling_wrapper) + # self._poller.daemon = True + # self._poller.start() + # await self._poller_running + + cdef _polling(self): + cdef grpc_event event + cdef CallbackContext *context + cdef object waiter + grpc_call_soon_threadsafe(self._poller_running.set_result, None) + + while not self._shutdown: + _LOGGER.debug('BackgroundCompletionQueue polling') + with nogil: + event = grpc_completion_queue_next(self._cq, + _GPR_INF_FUTURE, + NULL) + _LOGGER.debug('BackgroundCompletionQueue polling 1') + + if event.type == GRPC_QUEUE_TIMEOUT: + _LOGGER.debug('BackgroundCompletionQueue timeout???') + raise NotImplementedError() + elif event.type == GRPC_QUEUE_SHUTDOWN: + _LOGGER.debug('BackgroundCompletionQueue shutdown!') + self._shutdown = True + grpc_call_soon_threadsafe(self._shutdown_completed.set_result, None) + else: + _LOGGER.debug('BackgroundCompletionQueue event! %d', event.success) + context = event.tag + grpc_call_soon_threadsafe( + _handle_callback_wrapper, + context.callback_wrapper, + event.success) + _LOGGER.debug('BackgroundCompletionQueue polling 2') + + def _polling_wrapper(self): + self._polling() + + async def shutdown(self): + grpc_completion_queue_shutdown(self._cq) + await self._shutdown_completed + grpc_completion_queue_destroy(self._cq) + + cdef grpc_completion_queue* c_ptr(self): + return self._cq diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi index daf1ffaf72f..54d75d99890 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi @@ -51,7 +51,8 @@ cdef enum AioServerStatus: cdef class AioServer: cdef Server _server - cdef CallbackCompletionQueue _cq + # cdef CallbackCompletionQueue _cq + cdef BackgroundCompletionQueue _cq cdef list _generic_handlers cdef AioServerStatus _status cdef object _loop # asyncio.EventLoop diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi index 3f08a06d183..5adb8a949fe 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi @@ -613,7 +613,8 @@ cdef class AioServer: # 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._cq = CallbackCompletionQueue() + self._cq = BackgroundCompletionQueue() grpc_server_register_completion_queue( self._server.c_server, self._cq.c_ptr(), diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pxd b/src/python/grpcio/grpc/_cython/cygrpc.pxd index 5fd77e03eae..6e7b1aa64b0 100644 --- a/src/python/grpcio/grpc/_cython/cygrpc.pxd +++ b/src/python/grpcio/grpc/_cython/cygrpc.pxd @@ -45,6 +45,7 @@ IF UNAME_SYSNAME != "Windows": include "_cygrpc/aio/iomgr/socket.pxd.pxi" include "_cygrpc/aio/iomgr/timer.pxd.pxi" include "_cygrpc/aio/iomgr/resolver.pxd.pxi" +include "_cygrpc/aio/poller.pxd.pxi" include "_cygrpc/aio/rpc_status.pxd.pxi" include "_cygrpc/aio/grpc_aio.pxd.pxi" include "_cygrpc/aio/callback_common.pxd.pxi" diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pyx b/src/python/grpcio/grpc/_cython/cygrpc.pyx index 2e6401b01d1..0c6ab7e590f 100644 --- a/src/python/grpcio/grpc/_cython/cygrpc.pyx +++ b/src/python/grpcio/grpc/_cython/cygrpc.pyx @@ -72,6 +72,7 @@ include "_cygrpc/aio/iomgr/resolver.pyx.pxi" include "_cygrpc/aio/common.pyx.pxi" include "_cygrpc/aio/rpc_status.pyx.pxi" include "_cygrpc/aio/callback_common.pyx.pxi" +include "_cygrpc/aio/poller.pyx.pxi" include "_cygrpc/aio/grpc_aio.pyx.pxi" include "_cygrpc/aio/call.pyx.pxi" include "_cygrpc/aio/channel.pyx.pxi" diff --git a/src/python/grpcio_tests/tests_aio/unit/call_test.py b/src/python/grpcio_tests/tests_aio/unit/call_test.py index 74bbc04be2f..70eb32f7533 100644 --- a/src/python/grpcio_tests/tests_aio/unit/call_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/call_test.py @@ -47,338 +47,338 @@ class _MulticallableTestMixin(): await self._server.stop(None) -class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): +# class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): - async def test_call_to_string(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_to_string(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertTrue(str(call) is not None) - self.assertTrue(repr(call) is not None) +# self.assertTrue(str(call) is not None) +# self.assertTrue(repr(call) is not None) - response = await call +# response = await call - self.assertTrue(str(call) is not None) - self.assertTrue(repr(call) is not None) +# self.assertTrue(str(call) is not None) +# self.assertTrue(repr(call) is not None) - async def test_call_ok(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_ok(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertFalse(call.done()) +# self.assertFalse(call.done()) - response = await call +# response = await call - self.assertTrue(call.done()) - self.assertIsInstance(response, messages_pb2.SimpleResponse) - self.assertEqual(await call.code(), grpc.StatusCode.OK) +# self.assertTrue(call.done()) +# self.assertIsInstance(response, messages_pb2.SimpleResponse) +# self.assertEqual(await call.code(), grpc.StatusCode.OK) - # Response is cached at call object level, reentrance - # returns again the same response - response_retry = await call - self.assertIs(response, response_retry) +# # Response is cached at call object level, reentrance +# # returns again the same response +# response_retry = await call +# self.assertIs(response, response_retry) - async def test_call_rpc_error(self): - async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: - stub = test_pb2_grpc.TestServiceStub(channel) +# async def test_call_rpc_error(self): +# async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: +# stub = test_pb2_grpc.TestServiceStub(channel) - call = stub.UnaryCall(messages_pb2.SimpleRequest()) +# call = stub.UnaryCall(messages_pb2.SimpleRequest()) - with self.assertRaises(aio.AioRpcError) as exception_context: - await call +# with self.assertRaises(aio.AioRpcError) as exception_context: +# await call - self.assertEqual(grpc.StatusCode.UNAVAILABLE, - exception_context.exception.code()) +# self.assertEqual(grpc.StatusCode.UNAVAILABLE, +# exception_context.exception.code()) - self.assertTrue(call.done()) - self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) +# self.assertTrue(call.done()) +# self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) - async def test_call_code_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual(await call.code(), grpc.StatusCode.OK) +# async def test_call_code_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual(await call.code(), grpc.StatusCode.OK) - async def test_call_details_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual('', await call.details()) +# async def test_call_details_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual('', await call.details()) - async def test_call_initial_metadata_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual((), await call.initial_metadata()) +# async def test_call_initial_metadata_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual((), await call.initial_metadata()) - async def test_call_trailing_metadata_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual((), await call.trailing_metadata()) +# async def test_call_trailing_metadata_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual((), await call.trailing_metadata()) - async def test_call_initial_metadata_cancelable(self): - coro_started = asyncio.Event() - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_initial_metadata_cancelable(self): +# coro_started = asyncio.Event() +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - async def coro(): - coro_started.set() - await call.initial_metadata() +# async def coro(): +# coro_started.set() +# await call.initial_metadata() - task = self.loop.create_task(coro()) - await coro_started.wait() - task.cancel() +# task = self.loop.create_task(coro()) +# await coro_started.wait() +# task.cancel() - # Test that initial metadata can still be asked thought - # a cancellation happened with the previous task - self.assertEqual((), await call.initial_metadata()) +# # Test that initial metadata can still be asked thought +# # a cancellation happened with the previous task +# self.assertEqual((), await call.initial_metadata()) - async def test_call_initial_metadata_multiple_waiters(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_initial_metadata_multiple_waiters(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - async def coro(): - return await call.initial_metadata() +# async def coro(): +# return await call.initial_metadata() - task1 = self.loop.create_task(coro()) - task2 = self.loop.create_task(coro()) +# task1 = self.loop.create_task(coro()) +# task2 = self.loop.create_task(coro()) - await call +# await call - self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) +# self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) - async def test_call_code_cancelable(self): - coro_started = asyncio.Event() - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_code_cancelable(self): +# coro_started = asyncio.Event() +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - async def coro(): - coro_started.set() - await call.code() +# async def coro(): +# coro_started.set() +# await call.code() - task = self.loop.create_task(coro()) - await coro_started.wait() - task.cancel() +# task = self.loop.create_task(coro()) +# await coro_started.wait() +# task.cancel() - # Test that code can still be asked thought - # a cancellation happened with the previous task - self.assertEqual(grpc.StatusCode.OK, await call.code()) +# # Test that code can still be asked thought +# # a cancellation happened with the previous task +# self.assertEqual(grpc.StatusCode.OK, await call.code()) - async def test_call_code_multiple_waiters(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_code_multiple_waiters(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - async def coro(): - return await call.code() +# async def coro(): +# return await call.code() - task1 = self.loop.create_task(coro()) - task2 = self.loop.create_task(coro()) +# task1 = self.loop.create_task(coro()) +# task2 = self.loop.create_task(coro()) - await call +# await call - self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await - asyncio.gather(task1, task2)) +# self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await +# asyncio.gather(task1, task2)) - async def test_cancel_unary_unary(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_cancel_unary_unary(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertFalse(call.cancelled()) +# self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) +# self.assertTrue(call.cancel()) +# self.assertFalse(call.cancel()) - with self.assertRaises(asyncio.CancelledError): - await call +# with self.assertRaises(asyncio.CancelledError): +# await call - # The info in the RpcError should match the info in Call object. - self.assertTrue(call.cancelled()) - self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) - self.assertEqual(await call.details(), - 'Locally cancelled by application!') +# # The info in the RpcError should match the info in Call object. +# self.assertTrue(call.cancelled()) +# self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) +# self.assertEqual(await call.details(), +# 'Locally cancelled by application!') - async def test_cancel_unary_unary_in_task(self): - coro_started = asyncio.Event() - call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) +# async def test_cancel_unary_unary_in_task(self): +# coro_started = asyncio.Event() +# call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) - async def another_coro(): - coro_started.set() - await call +# async def another_coro(): +# coro_started.set() +# await call - task = self.loop.create_task(another_coro()) - await coro_started.wait() +# task = self.loop.create_task(another_coro()) +# await coro_started.wait() - self.assertFalse(task.done()) - task.cancel() +# self.assertFalse(task.done()) +# task.cancel() - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - with self.assertRaises(asyncio.CancelledError): - await task +# with self.assertRaises(asyncio.CancelledError): +# await task class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): - async def test_cancel_unary_stream(self): - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) - - response = await call.read() - self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertTrue(call.cancel()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - call.details()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - self.assertTrue(call.cancelled()) - - async def test_multiple_cancel_unary_stream(self): - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) - - response = await call.read() - self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) - self.assertFalse(call.cancel()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - - async def test_early_cancel_unary_stream(self): - """Test cancellation before receiving messages.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - - self.assertTrue(call.cancelled()) - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - call.details()) - - async def test_late_cancel_unary_stream(self): - """Test cancellation after received all messages.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - for _ in range(_NUM_STREAM_RESPONSES): - response = await call.read() - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # After all messages received, it is possible that the final state - # is received or on its way. It's basically a data race, so our - # expectation here is do not crash :) - call.cancel() - self.assertIn(await call.code(), - [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) - - async def test_too_many_reads_unary_stream(self): - """Test calling read after received all messages fails.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - for _ in range(_NUM_STREAM_RESPONSES): - response = await call.read() - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - self.assertIs(await call.read(), aio.EOF) - - # After the RPC is finished, further reads will lead to exception. - self.assertEqual(await call.code(), grpc.StatusCode.OK) - self.assertIs(await call.read(), aio.EOF) - - async def test_unary_stream_async_generator(self): - """Sunny day test case for unary_stream.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) - - async for response in call: - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - - async def test_cancel_unary_stream_in_task_using_read(self): - coro_started = asyncio.Event() - - # Configs the server method to block forever - request = messages_pb2.StreamingOutputCallRequest() - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_INFINITE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - async def another_coro(): - coro_started.set() - await call.read() - - task = self.loop.create_task(another_coro()) - await coro_started.wait() - - self.assertFalse(task.done()) - task.cancel() - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - with self.assertRaises(asyncio.CancelledError): - await task + # async def test_cancel_unary_stream(self): + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters( + # size=_RESPONSE_PAYLOAD_SIZE, + # interval_us=_RESPONSE_INTERVAL_US, + # )) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + # self.assertFalse(call.cancelled()) + + # response = await call.read() + # self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # self.assertTrue(call.cancel()) + # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + # self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + # call.details()) + # self.assertFalse(call.cancel()) + + # with self.assertRaises(asyncio.CancelledError): + # await call.read() + # self.assertTrue(call.cancelled()) + + # async def test_multiple_cancel_unary_stream(self): + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters( + # size=_RESPONSE_PAYLOAD_SIZE, + # interval_us=_RESPONSE_INTERVAL_US, + # )) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + # self.assertFalse(call.cancelled()) + + # response = await call.read() + # self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # self.assertTrue(call.cancel()) + # self.assertFalse(call.cancel()) + # self.assertFalse(call.cancel()) + # self.assertFalse(call.cancel()) + + # with self.assertRaises(asyncio.CancelledError): + # await call.read() + + # async def test_early_cancel_unary_stream(self): + # """Test cancellation before receiving messages.""" + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters( + # size=_RESPONSE_PAYLOAD_SIZE, + # interval_us=_RESPONSE_INTERVAL_US, + # )) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + + # self.assertFalse(call.cancelled()) + # self.assertTrue(call.cancel()) + # self.assertFalse(call.cancel()) + + # with self.assertRaises(asyncio.CancelledError): + # await call.read() + + # self.assertTrue(call.cancelled()) + + # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + # self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + # call.details()) + + # async def test_late_cancel_unary_stream(self): + # """Test cancellation after received all messages.""" + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + + # for _ in range(_NUM_STREAM_RESPONSES): + # response = await call.read() + # self.assertIs(type(response), + # messages_pb2.StreamingOutputCallResponse) + # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # # After all messages received, it is possible that the final state + # # is received or on its way. It's basically a data race, so our + # # expectation here is do not crash :) + # call.cancel() + # self.assertIn(await call.code(), + # [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) + + # async def test_too_many_reads_unary_stream(self): + # """Test calling read after received all messages fails.""" + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + + # for _ in range(_NUM_STREAM_RESPONSES): + # response = await call.read() + # self.assertIs(type(response), + # messages_pb2.StreamingOutputCallResponse) + # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + # self.assertIs(await call.read(), aio.EOF) + + # # After the RPC is finished, further reads will lead to exception. + # self.assertEqual(await call.code(), grpc.StatusCode.OK) + # self.assertIs(await call.read(), aio.EOF) + + # async def test_unary_stream_async_generator(self): + # """Sunny day test case for unary_stream.""" + # # Prepares the request + # request = messages_pb2.StreamingOutputCallRequest() + # for _ in range(_NUM_STREAM_RESPONSES): + # request.response_parameters.append( + # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + # self.assertFalse(call.cancelled()) + + # async for response in call: + # self.assertIs(type(response), + # messages_pb2.StreamingOutputCallResponse) + # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # self.assertEqual(await call.code(), grpc.StatusCode.OK) + + # async def test_cancel_unary_stream_in_task_using_read(self): + # coro_started = asyncio.Event() + + # # Configs the server method to block forever + # request = messages_pb2.StreamingOutputCallRequest() + # request.response_parameters.append( + # messages_pb2.ResponseParameters( + # size=_RESPONSE_PAYLOAD_SIZE, + # interval_us=_INFINITE_INTERVAL_US, + # )) + + # # Invokes the actual RPC + # call = self._stub.StreamingOutputCall(request) + + # async def another_coro(): + # coro_started.set() + # await call.read() + + # task = self.loop.create_task(another_coro()) + # await coro_started.wait() + + # self.assertFalse(task.done()) + # task.cancel() + + # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + # with self.assertRaises(asyncio.CancelledError): + # await task async def test_cancel_unary_stream_in_task_using_async_for(self): coro_started = asyncio.Event() @@ -755,5 +755,5 @@ class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): if __name__ == '__main__': - logging.basicConfig() + logging.basicConfig(level=logging.DEBUG) unittest.main(verbosity=2) From 464d41f4b8d1a7346c186786022ca9aa6ec9333f Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 14:08:56 -0800 Subject: [PATCH 02/14] Great. Everything seems working. --- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 8 +- .../grpc/_cython/_cygrpc/aio/poller.pyx.pxi | 9 +- .../grpcio/grpc/experimental/aio/_server.py | 2 +- .../grpcio_tests/tests_aio/unit/call_test.py | 562 +++++++++--------- .../tests_aio/unit/server_test.py | 2 - 5 files changed, 288 insertions(+), 295 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index 95c6caf2501..16fc967b82e 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -61,7 +61,7 @@ def grpc_aio_loop(): return _grpc_aio_loop -cdef grpc_schedule_coroutine(object coro): +def grpc_schedule_coroutine(object coro): """Thread-safely schedules coroutine to gRPC Aio event loop. If invoked within the same thread as the event loop, return an @@ -69,8 +69,10 @@ cdef grpc_schedule_coroutine(object coro): Future). For non-asyncio threads, sync Future objects are probably easier to handle (without worrying other thread-safety stuff). """ - assert _event_loop_thread_ident != threading.current_thread().ident - return asyncio.run_coroutine_threadsafe(coro, _grpc_aio_loop) + if _event_loop_thread_ident != threading.current_thread().ident: + return asyncio.run_coroutine_threadsafe(coro, _grpc_aio_loop) + else: + return _grpc_aio_loop.create_task(coro) def grpc_call_soon_threadsafe(object func, *args): diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi index 71d429139a4..ce6df5c90d1 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi @@ -16,13 +16,7 @@ cdef gpr_timespec _GPR_INF_FUTURE = gpr_inf_future(GPR_CLOCK_REALTIME) def _handle_callback_wrapper(CallbackWrapper callback_wrapper, int success): - try: - CallbackWrapper.functor_run(callback_wrapper.c_functor(), success) - _LOGGER.debug('_handle_callback_wrapper Done') - except Exception as e: - _LOGGER.debug('_handle_callback_wrapper EXP') - _LOGGER.exception(e) - raise + CallbackWrapper.functor_run(callback_wrapper.c_functor(), success) cdef class BackgroundCompletionQueue: @@ -33,7 +27,6 @@ cdef class BackgroundCompletionQueue: self._shutdown_completed = asyncio.get_event_loop().create_future() self._poller = None self._poller_running = asyncio.get_event_loop().create_future() - # asyncio.get_event_loop().create_task(self._start_poller()) self._poller = threading.Thread(target=self._polling_wrapper) self._poller.daemon = True self._poller.start() diff --git a/src/python/grpcio/grpc/experimental/aio/_server.py b/src/python/grpcio/grpc/experimental/aio/_server.py index 18e2bf65553..ec0af1d51f3 100644 --- a/src/python/grpcio/grpc/experimental/aio/_server.py +++ b/src/python/grpcio/grpc/experimental/aio/_server.py @@ -162,7 +162,7 @@ class Server(_base_server.Server): be safe to slightly extend the underlying Cython object's life span. """ if hasattr(self, '_server'): - self._loop.create_task(self._server.shutdown(None)) + cygrpc.grpc_schedule_coroutine(self._server.shutdown(None)) def server(migration_thread_pool: Optional[Executor] = None, diff --git a/src/python/grpcio_tests/tests_aio/unit/call_test.py b/src/python/grpcio_tests/tests_aio/unit/call_test.py index 70eb32f7533..e47c00a62ab 100644 --- a/src/python/grpcio_tests/tests_aio/unit/call_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/call_test.py @@ -47,338 +47,338 @@ class _MulticallableTestMixin(): await self._server.stop(None) -# class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): +class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): -# async def test_call_to_string(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_to_string(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertTrue(str(call) is not None) -# self.assertTrue(repr(call) is not None) + self.assertTrue(str(call) is not None) + self.assertTrue(repr(call) is not None) -# response = await call + response = await call -# self.assertTrue(str(call) is not None) -# self.assertTrue(repr(call) is not None) + self.assertTrue(str(call) is not None) + self.assertTrue(repr(call) is not None) -# async def test_call_ok(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_ok(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertFalse(call.done()) + self.assertFalse(call.done()) -# response = await call + response = await call -# self.assertTrue(call.done()) -# self.assertIsInstance(response, messages_pb2.SimpleResponse) -# self.assertEqual(await call.code(), grpc.StatusCode.OK) + self.assertTrue(call.done()) + self.assertIsInstance(response, messages_pb2.SimpleResponse) + self.assertEqual(await call.code(), grpc.StatusCode.OK) -# # Response is cached at call object level, reentrance -# # returns again the same response -# response_retry = await call -# self.assertIs(response, response_retry) + # Response is cached at call object level, reentrance + # returns again the same response + response_retry = await call + self.assertIs(response, response_retry) -# async def test_call_rpc_error(self): -# async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: -# stub = test_pb2_grpc.TestServiceStub(channel) + async def test_call_rpc_error(self): + async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: + stub = test_pb2_grpc.TestServiceStub(channel) -# call = stub.UnaryCall(messages_pb2.SimpleRequest()) + call = stub.UnaryCall(messages_pb2.SimpleRequest()) -# with self.assertRaises(aio.AioRpcError) as exception_context: -# await call + with self.assertRaises(aio.AioRpcError) as exception_context: + await call -# self.assertEqual(grpc.StatusCode.UNAVAILABLE, -# exception_context.exception.code()) + self.assertEqual(grpc.StatusCode.UNAVAILABLE, + exception_context.exception.code()) -# self.assertTrue(call.done()) -# self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) + self.assertTrue(call.done()) + self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) -# async def test_call_code_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual(await call.code(), grpc.StatusCode.OK) + async def test_call_code_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual(await call.code(), grpc.StatusCode.OK) -# async def test_call_details_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual('', await call.details()) + async def test_call_details_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual('', await call.details()) -# async def test_call_initial_metadata_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual((), await call.initial_metadata()) + async def test_call_initial_metadata_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual((), await call.initial_metadata()) -# async def test_call_trailing_metadata_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual((), await call.trailing_metadata()) + async def test_call_trailing_metadata_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual((), await call.trailing_metadata()) -# async def test_call_initial_metadata_cancelable(self): -# coro_started = asyncio.Event() -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_initial_metadata_cancelable(self): + coro_started = asyncio.Event() + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def coro(): -# coro_started.set() -# await call.initial_metadata() + async def coro(): + coro_started.set() + await call.initial_metadata() -# task = self.loop.create_task(coro()) -# await coro_started.wait() -# task.cancel() + task = self.loop.create_task(coro()) + await coro_started.wait() + task.cancel() -# # Test that initial metadata can still be asked thought -# # a cancellation happened with the previous task -# self.assertEqual((), await call.initial_metadata()) + # Test that initial metadata can still be asked thought + # a cancellation happened with the previous task + self.assertEqual((), await call.initial_metadata()) -# async def test_call_initial_metadata_multiple_waiters(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_initial_metadata_multiple_waiters(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def coro(): -# return await call.initial_metadata() + async def coro(): + return await call.initial_metadata() -# task1 = self.loop.create_task(coro()) -# task2 = self.loop.create_task(coro()) + task1 = self.loop.create_task(coro()) + task2 = self.loop.create_task(coro()) -# await call + await call -# self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) + self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) -# async def test_call_code_cancelable(self): -# coro_started = asyncio.Event() -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_code_cancelable(self): + coro_started = asyncio.Event() + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def coro(): -# coro_started.set() -# await call.code() + async def coro(): + coro_started.set() + await call.code() -# task = self.loop.create_task(coro()) -# await coro_started.wait() -# task.cancel() + task = self.loop.create_task(coro()) + await coro_started.wait() + task.cancel() -# # Test that code can still be asked thought -# # a cancellation happened with the previous task -# self.assertEqual(grpc.StatusCode.OK, await call.code()) + # Test that code can still be asked thought + # a cancellation happened with the previous task + self.assertEqual(grpc.StatusCode.OK, await call.code()) -# async def test_call_code_multiple_waiters(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_code_multiple_waiters(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def coro(): -# return await call.code() + async def coro(): + return await call.code() -# task1 = self.loop.create_task(coro()) -# task2 = self.loop.create_task(coro()) + task1 = self.loop.create_task(coro()) + task2 = self.loop.create_task(coro()) -# await call + await call -# self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await -# asyncio.gather(task1, task2)) + self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await + asyncio.gather(task1, task2)) -# async def test_cancel_unary_unary(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_cancel_unary_unary(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertFalse(call.cancelled()) + self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertFalse(call.cancel()) + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) -# with self.assertRaises(asyncio.CancelledError): -# await call + with self.assertRaises(asyncio.CancelledError): + await call -# # The info in the RpcError should match the info in Call object. -# self.assertTrue(call.cancelled()) -# self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) -# self.assertEqual(await call.details(), -# 'Locally cancelled by application!') + # The info in the RpcError should match the info in Call object. + self.assertTrue(call.cancelled()) + self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) + self.assertEqual(await call.details(), + 'Locally cancelled by application!') -# async def test_cancel_unary_unary_in_task(self): -# coro_started = asyncio.Event() -# call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) + async def test_cancel_unary_unary_in_task(self): + coro_started = asyncio.Event() + call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) -# async def another_coro(): -# coro_started.set() -# await call + async def another_coro(): + coro_started.set() + await call -# task = self.loop.create_task(another_coro()) -# await coro_started.wait() + task = self.loop.create_task(another_coro()) + await coro_started.wait() -# self.assertFalse(task.done()) -# task.cancel() + self.assertFalse(task.done()) + task.cancel() -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# with self.assertRaises(asyncio.CancelledError): -# await task + with self.assertRaises(asyncio.CancelledError): + await task class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): - # async def test_cancel_unary_stream(self): - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters( - # size=_RESPONSE_PAYLOAD_SIZE, - # interval_us=_RESPONSE_INTERVAL_US, - # )) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - # self.assertFalse(call.cancelled()) - - # response = await call.read() - # self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # self.assertTrue(call.cancel()) - # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - # self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - # call.details()) - # self.assertFalse(call.cancel()) - - # with self.assertRaises(asyncio.CancelledError): - # await call.read() - # self.assertTrue(call.cancelled()) - - # async def test_multiple_cancel_unary_stream(self): - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters( - # size=_RESPONSE_PAYLOAD_SIZE, - # interval_us=_RESPONSE_INTERVAL_US, - # )) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - # self.assertFalse(call.cancelled()) - - # response = await call.read() - # self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # self.assertTrue(call.cancel()) - # self.assertFalse(call.cancel()) - # self.assertFalse(call.cancel()) - # self.assertFalse(call.cancel()) - - # with self.assertRaises(asyncio.CancelledError): - # await call.read() - - # async def test_early_cancel_unary_stream(self): - # """Test cancellation before receiving messages.""" - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters( - # size=_RESPONSE_PAYLOAD_SIZE, - # interval_us=_RESPONSE_INTERVAL_US, - # )) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - - # self.assertFalse(call.cancelled()) - # self.assertTrue(call.cancel()) - # self.assertFalse(call.cancel()) - - # with self.assertRaises(asyncio.CancelledError): - # await call.read() - - # self.assertTrue(call.cancelled()) - - # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - # self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - # call.details()) - - # async def test_late_cancel_unary_stream(self): - # """Test cancellation after received all messages.""" - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - - # for _ in range(_NUM_STREAM_RESPONSES): - # response = await call.read() - # self.assertIs(type(response), - # messages_pb2.StreamingOutputCallResponse) - # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # # After all messages received, it is possible that the final state - # # is received or on its way. It's basically a data race, so our - # # expectation here is do not crash :) - # call.cancel() - # self.assertIn(await call.code(), - # [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) - - # async def test_too_many_reads_unary_stream(self): - # """Test calling read after received all messages fails.""" - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - - # for _ in range(_NUM_STREAM_RESPONSES): - # response = await call.read() - # self.assertIs(type(response), - # messages_pb2.StreamingOutputCallResponse) - # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - # self.assertIs(await call.read(), aio.EOF) - - # # After the RPC is finished, further reads will lead to exception. - # self.assertEqual(await call.code(), grpc.StatusCode.OK) - # self.assertIs(await call.read(), aio.EOF) - - # async def test_unary_stream_async_generator(self): - # """Sunny day test case for unary_stream.""" - # # Prepares the request - # request = messages_pb2.StreamingOutputCallRequest() - # for _ in range(_NUM_STREAM_RESPONSES): - # request.response_parameters.append( - # messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - # self.assertFalse(call.cancelled()) - - # async for response in call: - # self.assertIs(type(response), - # messages_pb2.StreamingOutputCallResponse) - # self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # self.assertEqual(await call.code(), grpc.StatusCode.OK) - - # async def test_cancel_unary_stream_in_task_using_read(self): - # coro_started = asyncio.Event() - - # # Configs the server method to block forever - # request = messages_pb2.StreamingOutputCallRequest() - # request.response_parameters.append( - # messages_pb2.ResponseParameters( - # size=_RESPONSE_PAYLOAD_SIZE, - # interval_us=_INFINITE_INTERVAL_US, - # )) - - # # Invokes the actual RPC - # call = self._stub.StreamingOutputCall(request) - - # async def another_coro(): - # coro_started.set() - # await call.read() - - # task = self.loop.create_task(another_coro()) - # await coro_started.wait() - - # self.assertFalse(task.done()) - # task.cancel() - - # self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - # with self.assertRaises(asyncio.CancelledError): - # await task + async def test_cancel_unary_stream(self): + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) + + response = await call.read() + self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertTrue(call.cancel()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + call.details()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + self.assertTrue(call.cancelled()) + + async def test_multiple_cancel_unary_stream(self): + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) + + response = await call.read() + self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) + self.assertFalse(call.cancel()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + + async def test_early_cancel_unary_stream(self): + """Test cancellation before receiving messages.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + + self.assertTrue(call.cancelled()) + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + call.details()) + + async def test_late_cancel_unary_stream(self): + """Test cancellation after received all messages.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + for _ in range(_NUM_STREAM_RESPONSES): + response = await call.read() + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # After all messages received, it is possible that the final state + # is received or on its way. It's basically a data race, so our + # expectation here is do not crash :) + call.cancel() + self.assertIn(await call.code(), + [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) + + async def test_too_many_reads_unary_stream(self): + """Test calling read after received all messages fails.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + for _ in range(_NUM_STREAM_RESPONSES): + response = await call.read() + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + self.assertIs(await call.read(), aio.EOF) + + # After the RPC is finished, further reads will lead to exception. + self.assertEqual(await call.code(), grpc.StatusCode.OK) + self.assertIs(await call.read(), aio.EOF) + + async def test_unary_stream_async_generator(self): + """Sunny day test case for unary_stream.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) + + async for response in call: + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + + async def test_cancel_unary_stream_in_task_using_read(self): + coro_started = asyncio.Event() + + # Configs the server method to block forever + request = messages_pb2.StreamingOutputCallRequest() + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_INFINITE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + async def another_coro(): + coro_started.set() + await call.read() + + task = self.loop.create_task(another_coro()) + await coro_started.wait() + + self.assertFalse(task.done()) + task.cancel() + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + with self.assertRaises(asyncio.CancelledError): + await task async def test_cancel_unary_stream_in_task_using_async_for(self): coro_started = asyncio.Event() diff --git a/src/python/grpcio_tests/tests_aio/unit/server_test.py b/src/python/grpcio_tests/tests_aio/unit/server_test.py index 70240fefee1..e2a2ebacf5b 100644 --- a/src/python/grpcio_tests/tests_aio/unit/server_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/server_test.py @@ -352,7 +352,6 @@ class TestServer(AioTestBase): await call self.assertEqual(grpc.StatusCode.UNAVAILABLE, exception_context.exception.code()) - self.assertIn('GOAWAY', exception_context.exception.details()) async def test_concurrent_graceful_shutdown(self): call = self._channel.unary_unary(_BLOCK_BRIEFLY)(_REQUEST) @@ -388,7 +387,6 @@ class TestServer(AioTestBase): await call self.assertEqual(grpc.StatusCode.UNAVAILABLE, exception_context.exception.code()) - self.assertIn('GOAWAY', exception_context.exception.details()) @unittest.skip('https://github.com/grpc/grpc/issues/20818') async def test_shutdown_before_call(self): From b2839c2bae317d6000fe51a994262a9fd637188b Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 14:21:07 -0800 Subject: [PATCH 03/14] Add assertion in grpc_call_soon_threadsafe --- src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index 16fc967b82e..284a6c9a84a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -76,4 +76,7 @@ def grpc_schedule_coroutine(object coro): def grpc_call_soon_threadsafe(object func, *args): + # TODO(lidiz) After we are confident, we can drop this assert. Otherwsie, + # we should limit this function to non-grpc-event-loop thread. + assert _event_loop_thread_ident != threading.current_thread().ident return _grpc_aio_loop.call_soon_threadsafe(func, *args) From efd483311cca31915cb22d98da49fda8dbeed2ed Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 14:25:52 -0800 Subject: [PATCH 04/14] Remove loggings --- .../grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi | 3 --- .../grpc/_cython/_cygrpc/aio/poller.pyx.pxi | 15 --------------- 2 files changed, 18 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi index 11b3653e0bf..65ee6e24e59 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi @@ -159,7 +159,6 @@ cdef class _AsyncioSocket: return self._reader and not self._reader._transport.is_closing() cdef void close(self): - _LOGGER.debug('closed!') if self.is_connected(): self._writer.close() if self._server: @@ -197,9 +196,7 @@ cdef class _AsyncioSocket: self._new_connection_callback, sock=self._py_socket, ) - _LOGGER.debug('start listen') - _LOGGER.debug('want to listen') grpc_aio_loop().create_task(create_asyncio_server()) cdef accept(self, diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi index ce6df5c90d1..51bede8ede5 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi @@ -31,15 +31,6 @@ cdef class BackgroundCompletionQueue: self._poller.daemon = True self._poller.start() - # async def _start_poller(self): - # if self._poller: - # raise UsageError('Poller can only be started once.') - - # self._poller = threading.Thread(target=self._polling_wrapper) - # self._poller.daemon = True - # self._poller.start() - # await self._poller_running - cdef _polling(self): cdef grpc_event event cdef CallbackContext *context @@ -47,28 +38,22 @@ cdef class BackgroundCompletionQueue: grpc_call_soon_threadsafe(self._poller_running.set_result, None) while not self._shutdown: - _LOGGER.debug('BackgroundCompletionQueue polling') with nogil: event = grpc_completion_queue_next(self._cq, _GPR_INF_FUTURE, NULL) - _LOGGER.debug('BackgroundCompletionQueue polling 1') if event.type == GRPC_QUEUE_TIMEOUT: - _LOGGER.debug('BackgroundCompletionQueue timeout???') raise NotImplementedError() elif event.type == GRPC_QUEUE_SHUTDOWN: - _LOGGER.debug('BackgroundCompletionQueue shutdown!') self._shutdown = True grpc_call_soon_threadsafe(self._shutdown_completed.set_result, None) else: - _LOGGER.debug('BackgroundCompletionQueue event! %d', event.success) context = event.tag grpc_call_soon_threadsafe( _handle_callback_wrapper, context.callback_wrapper, event.success) - _LOGGER.debug('BackgroundCompletionQueue polling 2') def _polling_wrapper(self): self._polling() From 221a50bf87269944907098f516424caf5f52304c Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 15:03:44 -0800 Subject: [PATCH 05/14] Allow multiple asyncio engine --- .../_cygrpc/aio/callback_common.pxd.pxi | 8 ---- .../_cygrpc/aio/callback_common.pyx.pxi | 21 ---------- .../grpc/_cython/_cygrpc/aio/channel.pxd.pxi | 3 +- .../grpc/_cython/_cygrpc/aio/channel.pyx.pxi | 3 +- ...oller.pxd.pxi => completion_queue.pxd.pxi} | 13 +++++- ...oller.pyx.pxi => completion_queue.pyx.pxi} | 41 ++++++++++++++++++- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 41 ++++++++++++++----- .../grpc/_cython/_cygrpc/aio/server.pxd.pxi | 3 +- .../grpc/_cython/_cygrpc/aio/server.pyx.pxi | 5 +-- src/python/grpcio/grpc/_cython/cygrpc.pxd | 2 +- src/python/grpcio/grpc/_cython/cygrpc.pyx | 3 +- tools/bazel.rc | 2 + .../linux/grpc_python_bazel_test_in_docker.sh | 1 + 13 files changed, 92 insertions(+), 54 deletions(-) rename src/python/grpcio/grpc/_cython/_cygrpc/aio/{poller.pxd.pxi => completion_queue.pxd.pxi} (51%) rename src/python/grpcio/grpc/_cython/_cygrpc/aio/{poller.pyx.pxi => completion_queue.pyx.pxi} (67%) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pxd.pxi index e99f78a18af..70a1c9b3f27 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pxd.pxi @@ -52,13 +52,5 @@ cdef class CallbackWrapper: cdef grpc_experimental_completion_queue_functor *c_functor(self) -cdef class CallbackCompletionQueue: - cdef grpc_completion_queue *_cq - cdef object _shutdown_completed # asyncio.Future - cdef CallbackWrapper _wrapper - - cdef grpc_completion_queue* c_ptr(self) - - cdef class GrpcCallWrapper: cdef grpc_call* call diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pyx.pxi index 24c79533012..33713b8ad64 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pyx.pxi @@ -69,27 +69,6 @@ cdef CallbackFailureHandler CQ_SHUTDOWN_FAILURE_HANDLER = CallbackFailureHandler InternalError) -cdef class CallbackCompletionQueue: - - def __cinit__(self): - self._shutdown_completed = grpc_aio_loop().create_future() - self._wrapper = CallbackWrapper( - self._shutdown_completed, - CQ_SHUTDOWN_FAILURE_HANDLER) - self._cq = grpc_completion_queue_create_for_callback( - 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) - - class ExecuteBatchError(Exception): pass diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi index 62215b2d24f..569e6763c54 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pxd.pxi @@ -21,8 +21,7 @@ cdef enum AioChannelStatus: cdef class AioChannel: cdef: grpc_channel * channel - # CallbackCompletionQueue cq - BackgroundCompletionQueue cq + BaseCompletionQueue cq object loop bytes _target AioChannelStatus _status diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi index cc6c5d99306..fa99371b211 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/channel.pyx.pxi @@ -31,8 +31,7 @@ cdef class AioChannel: options = () cdef _ChannelArgs channel_args = _ChannelArgs(options) self._target = target - # self.cq = CallbackCompletionQueue() - self.cq = BackgroundCompletionQueue() + self.cq = create_completion_queue() self.loop = loop self._status = AIO_CHANNEL_STATUS_READY diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi similarity index 51% rename from src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi rename to src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi index c66d24bd502..b5b8c5036cd 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi @@ -1,6 +1,10 @@ cdef gpr_timespec _GPR_INF_FUTURE = gpr_inf_future(GPR_CLOCK_REALTIME) -cdef class BackgroundCompletionQueue: +cdef class BaseCompletionQueue: + + cdef grpc_completion_queue* c_ptr(self) + +cdef class PollerCompletionQueue(BaseCompletionQueue): cdef grpc_completion_queue *_cq cdef bint _shutdown cdef object _shutdown_completed @@ -8,4 +12,9 @@ cdef class BackgroundCompletionQueue: cdef object _poller_running cdef _polling(self) - cdef grpc_completion_queue* c_ptr(self) + + +cdef class CallbackCompletionQueue(BaseCompletionQueue): + cdef grpc_completion_queue *_cq + cdef object _shutdown_completed # asyncio.Future + cdef CallbackWrapper _wrapper diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi similarity index 67% rename from src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi rename to src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index 51bede8ede5..85ae0c4561a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/poller.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -19,7 +19,16 @@ def _handle_callback_wrapper(CallbackWrapper callback_wrapper, int success): CallbackWrapper.functor_run(callback_wrapper.c_functor(), success) -cdef class BackgroundCompletionQueue: +cdef class BaseCompletionQueue: + + async def shutdown(self): + raise NotImplementedError() + + cdef grpc_completion_queue* c_ptr(self): + raise NotImplementedError() + + +cdef class PollerCompletionQueue(BaseCompletionQueue): def __cinit__(self): self._cq = grpc_completion_queue_create_for_next(NULL) @@ -65,3 +74,33 @@ cdef class BackgroundCompletionQueue: cdef grpc_completion_queue* c_ptr(self): return self._cq + + +cdef class CallbackCompletionQueue(BaseCompletionQueue): + + def __cinit__(self): + self._shutdown_completed = grpc_aio_loop().create_future() + self._wrapper = CallbackWrapper( + self._shutdown_completed, + CQ_SHUTDOWN_FAILURE_HANDLER) + self._cq = grpc_completion_queue_create_for_callback( + 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 BaseCompletionQueue create_completion_queue(): + if grpc_aio_engine is AsyncIOEngine.CUSTOM_IO_MANAGER: + return CallbackCompletionQueue() + elif grpc_aio_engine is AsyncIOEngine.POLLER: + return PollerCompletionQueue() + else: + raise ValueError('Unexpected engine type [%s]' % grpc_aio_engine) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index 284a6c9a84a..44064ef95ce 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -20,38 +20,57 @@ cdef bint _grpc_aio_initialized = False # a single event loop picked by "init_grpc_aio". cdef object _grpc_aio_loop cdef object _event_loop_thread_ident +cdef str _GRPC_ASYNCIO_ENGINE = os.environ.get('GRPC_ASYNCIO_ENGINE', 'default').lower() +grpc_aio_engine = None + + +class AsyncIOEngine(enum.Enum): + DEFAULT = 'default' + CUSTOM_IO_MANAGER = 'custom' + CQ_POLLER = 'poller' def init_grpc_aio(): global _grpc_aio_initialized global _grpc_aio_loop global _event_loop_thread_ident + global grpc_aio_engine + # Marks this function as called if _grpc_aio_initialized: return else: _grpc_aio_initialized = True - _event_loop_thread_ident = threading.current_thread().ident + + # Picks the engine for gRPC AsyncIO Stack + for engine_type in AsyncIOEngine: + if engine_type.value == _GRPC_ASYNCIO_ENGINE: + grpc_aio_engine = engine_type + break + if grpc_aio_engine is None or grpc_aio_engine is AsyncIOEngine.DEFAULT: + grpc_aio_engine = AsyncIOEngine.CUSTOM_IO_MANAGER # Anchors the event loop that the gRPC library going to use. _grpc_aio_loop = asyncio.get_event_loop() - - # Activates asyncio IO manager - # install_asyncio_iomgr() + _event_loop_thread_ident = threading.current_thread().ident # TODO(https://github.com/grpc/grpc/issues/22244) we need a the # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC # library won't shutdown cleanly. grpc_init() - # Timers are triggered by the Asyncio loop. We disable - # the background thread that is being used by the native - # gRPC iomgr. - # grpc_timer_manager_set_threading(False) + if grpc_aio_engine is AsyncIOEngine.CUSTOM_IO_MANAGER: + # Activates asyncio IO manager + install_asyncio_iomgr() + + # Timers are triggered by the Asyncio loop. We disable + # the background thread that is being used by the native + # gRPC iomgr. + grpc_timer_manager_set_threading(False) - # gRPC callbaks are executed within the same thread used by the Asyncio - # event loop, as it is being done by the other Asyncio callbacks. - # Executor.SetThreadingAll(False) + # gRPC callbaks are executed within the same thread used by the Asyncio + # event loop, as it is being done by the other Asyncio callbacks. + Executor.SetThreadingAll(False) _grpc_aio_initialized = False diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi index 54d75d99890..18fc9214b27 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pxd.pxi @@ -51,8 +51,7 @@ cdef enum AioServerStatus: cdef class AioServer: cdef Server _server - # cdef CallbackCompletionQueue _cq - cdef BackgroundCompletionQueue _cq + cdef BaseCompletionQueue _cq cdef list _generic_handlers cdef AioServerStatus _status cdef object _loop # asyncio.EventLoop diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi index 5adb8a949fe..d8ed9a41fe7 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi @@ -613,8 +613,7 @@ cdef class AioServer: # 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._cq = BackgroundCompletionQueue() + self._cq = create_completion_queue() grpc_server_register_completion_queue( self._server.c_server, self._cq.c_ptr(), @@ -737,7 +736,7 @@ cdef class AioServer: # 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._cq.c_ptr(), self._shutdown_callback_wrapper.c_functor()) # Ensures the serving task (coroutine) exits. diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pxd b/src/python/grpcio/grpc/_cython/cygrpc.pxd index 6e7b1aa64b0..166be370227 100644 --- a/src/python/grpcio/grpc/_cython/cygrpc.pxd +++ b/src/python/grpcio/grpc/_cython/cygrpc.pxd @@ -45,7 +45,7 @@ IF UNAME_SYSNAME != "Windows": include "_cygrpc/aio/iomgr/socket.pxd.pxi" include "_cygrpc/aio/iomgr/timer.pxd.pxi" include "_cygrpc/aio/iomgr/resolver.pxd.pxi" -include "_cygrpc/aio/poller.pxd.pxi" +include "_cygrpc/aio/completion_queue.pxd.pxi" include "_cygrpc/aio/rpc_status.pxd.pxi" include "_cygrpc/aio/grpc_aio.pxd.pxi" include "_cygrpc/aio/callback_common.pxd.pxi" diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pyx b/src/python/grpcio/grpc/_cython/cygrpc.pyx index 0c6ab7e590f..5910f10abfd 100644 --- a/src/python/grpcio/grpc/_cython/cygrpc.pyx +++ b/src/python/grpcio/grpc/_cython/cygrpc.pyx @@ -20,6 +20,7 @@ import os import sys import threading import time +import enum import grpc @@ -71,8 +72,8 @@ include "_cygrpc/aio/iomgr/timer.pyx.pxi" include "_cygrpc/aio/iomgr/resolver.pyx.pxi" include "_cygrpc/aio/common.pyx.pxi" include "_cygrpc/aio/rpc_status.pyx.pxi" +include "_cygrpc/aio/completion_queue.pyx.pxi" include "_cygrpc/aio/callback_common.pyx.pxi" -include "_cygrpc/aio/poller.pyx.pxi" include "_cygrpc/aio/grpc_aio.pyx.pxi" include "_cygrpc/aio/call.pyx.pxi" include "_cygrpc/aio/channel.pyx.pxi" diff --git a/tools/bazel.rc b/tools/bazel.rc index 92307805017..6dbcc273223 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -89,3 +89,5 @@ build:basicprof --copt=-DGRPC_BASIC_PROFILER build:basicprof --copt=-DGRPC_TIMERS_RDTSC build:python_single_threaded_unary_stream --test_env="GRPC_SINGLE_THREADED_UNARY_STREAM=true" + +build:python_poller_engine --test_env="GRPC_ASYNCIO_ENGINE=poller" diff --git a/tools/internal_ci/linux/grpc_python_bazel_test_in_docker.sh b/tools/internal_ci/linux/grpc_python_bazel_test_in_docker.sh index fef8c8fa9f4..da286c433b5 100755 --- a/tools/internal_ci/linux/grpc_python_bazel_test_in_docker.sh +++ b/tools/internal_ci/linux/grpc_python_bazel_test_in_docker.sh @@ -28,6 +28,7 @@ TEST_TARGETS="//src/python/... //examples/python/..." BAZEL_FLAGS="--spawn_strategy=standalone --genrule_strategy=standalone --test_output=errors" bazel test ${BAZEL_FLAGS} ${TEST_TARGETS} bazel test --config=python_single_threaded_unary_stream ${BAZEL_FLAGS} ${TEST_TARGETS} +bazel test --config=python_poller_engine ${BAZEL_FLAGS} ${TEST_TARGETS} # TODO(https://github.com/grpc/grpc/issues/19854): Move this to a new Kokoro From 1d13ec88de853232febe07eefb6d3a1868e26e82 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 15:23:23 -0800 Subject: [PATCH 06/14] Segfault while shutting down --- .../_cython/_cygrpc/aio/completion_queue.pxd.pxi | 14 +++++++++++++- .../_cython/_cygrpc/aio/completion_queue.pyx.pxi | 1 + .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 2 +- .../grpc/_cython/_cygrpc/aio/iomgr/socket.pxd.pxi | 4 ++++ .../grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi | 14 +++++++++++++- .../grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi | 1 + 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi index b5b8c5036cd..708a2745fdf 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi @@ -1,4 +1,16 @@ -cdef gpr_timespec _GPR_INF_FUTURE = gpr_inf_future(GPR_CLOCK_REALTIME) +# Copyright 2020 The gRPC Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. cdef class BaseCompletionQueue: diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index 85ae0c4561a..4b8b326a2da 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -92,6 +92,7 @@ cdef class CallbackCompletionQueue(BaseCompletionQueue): return self._cq async def shutdown(self): + _LOGGER.debug('CallbackCompletionQueue shutdown') grpc_completion_queue_shutdown(self._cq) await self._shutdown_completed grpc_completion_queue_destroy(self._cq) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index 44064ef95ce..fe36a4e63aa 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -27,7 +27,7 @@ grpc_aio_engine = None class AsyncIOEngine(enum.Enum): DEFAULT = 'default' CUSTOM_IO_MANAGER = 'custom' - CQ_POLLER = 'poller' + POLLER = 'poller' def init_grpc_aio(): diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pxd.pxi index cd425d2e941..cfab5549b2e 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pxd.pxi @@ -24,10 +24,14 @@ cdef class _AsyncioSocket: object _task_read object _task_write object _task_connect + object _task_listen char * _read_buffer # Caches the picked event loop, so we can avoid the 30ns overhead each # time we need access to the event loop. object _loop + # TODO(lidiz) Drop after 3.6 deprecation. Python 3.7 introduces methods + # like `is_closing()` to help graceful shutdown. + bint _closed # Client-side attributes grpc_custom_connect_callback _grpc_connect_cb diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi index 65ee6e24e59..f46e657c263 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/socket.pyx.pxi @@ -31,10 +31,12 @@ cdef class _AsyncioSocket: self._task_connect = None self._task_read = None self._task_write = None + self._task_listen = None self._read_buffer = NULL self._server = None self._py_socket = None self._peername = None + self._closed = False @staticmethod cdef _AsyncioSocket create(grpc_custom_socket * grpc_socket, @@ -159,8 +161,14 @@ cdef class _AsyncioSocket: return self._reader and not self._reader._transport.is_closing() cdef void close(self): + if self._closed: + return + else: + self._closed = True if self.is_connected(): self._writer.close() + if self._task_listen and not self._task_listen.done(): + self._task_listen.close() if self._server: self._server.close() # NOTE(lidiz) If the asyncio.Server is created from a Python socket, @@ -170,6 +178,10 @@ cdef class _AsyncioSocket: self._py_socket.close() def _new_connection_callback(self, object reader, object writer): + # If the socket is closed, stop. + if self._closed: + return + # Close the connection if server is not started yet. if self._grpc_accept_cb == NULL: writer.close() @@ -197,7 +209,7 @@ cdef class _AsyncioSocket: sock=self._py_socket, ) - grpc_aio_loop().create_task(create_asyncio_server()) + self._task_listen = grpc_aio_loop().create_task(create_asyncio_server()) cdef accept(self, grpc_custom_socket* grpc_socket_client, diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi index d8ed9a41fe7..1b4f61a9c36 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi @@ -754,6 +754,7 @@ cdef class AioServer: grace: An optional float indicating the length of grace period in seconds. """ + _LOGGER.debug('server shutdown') if self._status == AIO_SERVER_STATUS_READY or self._status == AIO_SERVER_STATUS_STOPPED: return From 0ee33a476286ebaf6a73aaccb0eb56ca89fcd2db Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 15:40:02 -0800 Subject: [PATCH 07/14] Confusing --- .../_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi | 2 + .../_cygrpc/aio/iomgr/resolver.pyx.pxi | 3 + .../grpcio_tests/tests_aio/unit/call_test.py | 1216 ++++++++--------- 3 files changed, 611 insertions(+), 610 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi index 37ba5f0d346..1a3dd46dcf4 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi @@ -170,6 +170,7 @@ cdef grpc_error* asyncio_resolve( char* host, char* port, grpc_resolved_addresses** res) with gil: + _LOGGER.debug('asyncio_resolve') result = native_socket.getaddrinfo(host, port) res[0] = tuples_to_resolvaddr(result) @@ -178,6 +179,7 @@ cdef void asyncio_resolve_async( grpc_custom_resolver* grpc_resolver, char* host, char* port) with gil: + _LOGGER.debug('asyncio_resolve_async') resolver = _AsyncioResolver.create(grpc_resolver) resolver.resolve(host, port) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi index 7d47fa77b00..7897368bc83 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi @@ -32,7 +32,9 @@ cdef class _AsyncioResolver: async def _async_resolve(self, bytes host, bytes port): self._task_resolve = None try: + _LOGGER.debug('_AsyncioResolver before') resolved = await grpc_aio_loop().getaddrinfo(host, port) + _LOGGER.debug('_AsyncioResolver after') except Exception as e: grpc_custom_resolve_callback( self._grpc_resolver, @@ -50,6 +52,7 @@ cdef class _AsyncioResolver: cdef void resolve(self, char* host, char* port): assert not self._task_resolve + _LOGGER.debug('_AsyncioResolver resolve') self._task_resolve = grpc_aio_loop().create_task( self._async_resolve(host, port) ) diff --git a/src/python/grpcio_tests/tests_aio/unit/call_test.py b/src/python/grpcio_tests/tests_aio/unit/call_test.py index e47c00a62ab..a9ff5f5dca8 100644 --- a/src/python/grpcio_tests/tests_aio/unit/call_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/call_test.py @@ -49,32 +49,32 @@ class _MulticallableTestMixin(): class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): - async def test_call_to_string(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + # async def test_call_to_string(self): + # call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertTrue(str(call) is not None) - self.assertTrue(repr(call) is not None) + # self.assertTrue(str(call) is not None) + # self.assertTrue(repr(call) is not None) - response = await call + # response = await call - self.assertTrue(str(call) is not None) - self.assertTrue(repr(call) is not None) + # self.assertTrue(str(call) is not None) + # self.assertTrue(repr(call) is not None) - async def test_call_ok(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + # async def test_call_ok(self): + # call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertFalse(call.done()) + # self.assertFalse(call.done()) - response = await call + # response = await call - self.assertTrue(call.done()) - self.assertIsInstance(response, messages_pb2.SimpleResponse) - self.assertEqual(await call.code(), grpc.StatusCode.OK) + # self.assertTrue(call.done()) + # self.assertIsInstance(response, messages_pb2.SimpleResponse) + # self.assertEqual(await call.code(), grpc.StatusCode.OK) - # Response is cached at call object level, reentrance - # returns again the same response - response_retry = await call - self.assertIs(response, response_retry) + # # Response is cached at call object level, reentrance + # # returns again the same response + # response_retry = await call + # self.assertIs(response, response_retry) async def test_call_rpc_error(self): async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: @@ -91,668 +91,664 @@ class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): self.assertTrue(call.done()) self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) - async def test_call_code_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual(await call.code(), grpc.StatusCode.OK) - async def test_call_details_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual('', await call.details()) +# async def test_call_code_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual(await call.code(), grpc.StatusCode.OK) - async def test_call_initial_metadata_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual((), await call.initial_metadata()) +# async def test_call_details_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual('', await call.details()) - async def test_call_trailing_metadata_awaitable(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertEqual((), await call.trailing_metadata()) +# async def test_call_initial_metadata_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual((), await call.initial_metadata()) - async def test_call_initial_metadata_cancelable(self): - coro_started = asyncio.Event() - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# async def test_call_trailing_metadata_awaitable(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual((), await call.trailing_metadata()) - async def coro(): - coro_started.set() - await call.initial_metadata() +# async def test_call_initial_metadata_cancelable(self): +# coro_started = asyncio.Event() +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - task = self.loop.create_task(coro()) - await coro_started.wait() - task.cancel() +# async def coro(): +# coro_started.set() +# await call.initial_metadata() - # Test that initial metadata can still be asked thought - # a cancellation happened with the previous task - self.assertEqual((), await call.initial_metadata()) +# task = self.loop.create_task(coro()) +# await coro_started.wait() +# task.cancel() - async def test_call_initial_metadata_multiple_waiters(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# # Test that initial metadata can still be asked thought +# # a cancellation happened with the previous task +# self.assertEqual((), await call.initial_metadata()) - async def coro(): - return await call.initial_metadata() +# async def test_call_initial_metadata_multiple_waiters(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - task1 = self.loop.create_task(coro()) - task2 = self.loop.create_task(coro()) +# async def coro(): +# return await call.initial_metadata() - await call +# task1 = self.loop.create_task(coro()) +# task2 = self.loop.create_task(coro()) - self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) +# await call - async def test_call_code_cancelable(self): - coro_started = asyncio.Event() - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) - async def coro(): - coro_started.set() - await call.code() +# async def test_call_code_cancelable(self): +# coro_started = asyncio.Event() +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - task = self.loop.create_task(coro()) - await coro_started.wait() - task.cancel() +# async def coro(): +# coro_started.set() +# await call.code() - # Test that code can still be asked thought - # a cancellation happened with the previous task - self.assertEqual(grpc.StatusCode.OK, await call.code()) +# task = self.loop.create_task(coro()) +# await coro_started.wait() +# task.cancel() - async def test_call_code_multiple_waiters(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# # Test that code can still be asked thought +# # a cancellation happened with the previous task +# self.assertEqual(grpc.StatusCode.OK, await call.code()) - async def coro(): - return await call.code() +# async def test_call_code_multiple_waiters(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - task1 = self.loop.create_task(coro()) - task2 = self.loop.create_task(coro()) +# async def coro(): +# return await call.code() - await call +# task1 = self.loop.create_task(coro()) +# task2 = self.loop.create_task(coro()) - self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await - asyncio.gather(task1, task2)) +# await call - async def test_cancel_unary_unary(self): - call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) +# self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await +# asyncio.gather(task1, task2)) - self.assertFalse(call.cancelled()) +# async def test_cancel_unary_unary(self): +# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) +# self.assertFalse(call.cancelled()) - with self.assertRaises(asyncio.CancelledError): - await call +# self.assertTrue(call.cancel()) +# self.assertFalse(call.cancel()) - # The info in the RpcError should match the info in Call object. - self.assertTrue(call.cancelled()) - self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) - self.assertEqual(await call.details(), - 'Locally cancelled by application!') +# with self.assertRaises(asyncio.CancelledError): +# await call - async def test_cancel_unary_unary_in_task(self): - coro_started = asyncio.Event() - call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) +# # The info in the RpcError should match the info in Call object. +# self.assertTrue(call.cancelled()) +# self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) +# self.assertEqual(await call.details(), +# 'Locally cancelled by application!') - async def another_coro(): - coro_started.set() - await call +# async def test_cancel_unary_unary_in_task(self): +# coro_started = asyncio.Event() +# call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) - task = self.loop.create_task(another_coro()) - await coro_started.wait() +# async def another_coro(): +# coro_started.set() +# await call - self.assertFalse(task.done()) - task.cancel() +# task = self.loop.create_task(another_coro()) +# await coro_started.wait() - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# self.assertFalse(task.done()) +# task.cancel() - with self.assertRaises(asyncio.CancelledError): - await task +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# with self.assertRaises(asyncio.CancelledError): +# await task -class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): +# class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): - async def test_cancel_unary_stream(self): - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) +# async def test_cancel_unary_stream(self): +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) +# self.assertFalse(call.cancelled()) + +# response = await call.read() +# self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# self.assertTrue(call.cancel()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await +# call.details()) +# self.assertFalse(call.cancel()) + +# with self.assertRaises(asyncio.CancelledError): +# await call.read() +# self.assertTrue(call.cancelled()) + +# async def test_multiple_cancel_unary_stream(self): +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) +# self.assertFalse(call.cancelled()) + +# response = await call.read() +# self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# self.assertTrue(call.cancel()) +# self.assertFalse(call.cancel()) +# self.assertFalse(call.cancel()) +# self.assertFalse(call.cancel()) + +# with self.assertRaises(asyncio.CancelledError): +# await call.read() + +# async def test_early_cancel_unary_stream(self): +# """Test cancellation before receiving messages.""" +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) + +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertFalse(call.cancel()) + +# with self.assertRaises(asyncio.CancelledError): +# await call.read() + +# self.assertTrue(call.cancelled()) + +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await +# call.details()) + +# async def test_late_cancel_unary_stream(self): +# """Test cancellation after received all messages.""" +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) + +# for _ in range(_NUM_STREAM_RESPONSES): +# response = await call.read() +# self.assertIs(type(response), +# messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# # After all messages received, it is possible that the final state +# # is received or on its way. It's basically a data race, so our +# # expectation here is do not crash :) +# call.cancel() +# self.assertIn(await call.code(), +# [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) + +# async def test_too_many_reads_unary_stream(self): +# """Test calling read after received all messages fails.""" +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) + +# for _ in range(_NUM_STREAM_RESPONSES): +# response = await call.read() +# self.assertIs(type(response), +# messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# self.assertIs(await call.read(), aio.EOF) + +# # After the RPC is finished, further reads will lead to exception. +# self.assertEqual(await call.code(), grpc.StatusCode.OK) +# self.assertIs(await call.read(), aio.EOF) + +# async def test_unary_stream_async_generator(self): +# """Sunny day test case for unary_stream.""" +# # Prepares the request +# request = messages_pb2.StreamingOutputCallRequest() +# for _ in range(_NUM_STREAM_RESPONSES): +# request.response_parameters.append( +# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) +# self.assertFalse(call.cancelled()) + +# async for response in call: +# self.assertIs(type(response), +# messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# self.assertEqual(await call.code(), grpc.StatusCode.OK) + +# async def test_cancel_unary_stream_in_task_using_read(self): +# coro_started = asyncio.Event() + +# # Configs the server method to block forever +# request = messages_pb2.StreamingOutputCallRequest() +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_INFINITE_INTERVAL_US, +# )) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) + +# async def another_coro(): +# coro_started.set() +# await call.read() + +# task = self.loop.create_task(another_coro()) +# await coro_started.wait() + +# self.assertFalse(task.done()) +# task.cancel() + +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# with self.assertRaises(asyncio.CancelledError): +# await task + +# async def test_cancel_unary_stream_in_task_using_async_for(self): +# coro_started = asyncio.Event() + +# # Configs the server method to block forever +# request = messages_pb2.StreamingOutputCallRequest() +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_INFINITE_INTERVAL_US, +# )) + +# # Invokes the actual RPC +# call = self._stub.StreamingOutputCall(request) + +# async def another_coro(): +# coro_started.set() +# async for _ in call: +# pass + +# task = self.loop.create_task(another_coro()) +# await coro_started.wait() + +# self.assertFalse(task.done()) +# task.cancel() + +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# with self.assertRaises(asyncio.CancelledError): +# await task - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) +# def test_call_credentials(self): - response = await call.read() - self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# class DummyAuth(grpc.AuthMetadataPlugin): - self.assertTrue(call.cancel()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - call.details()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - self.assertTrue(call.cancelled()) - - async def test_multiple_cancel_unary_stream(self): - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) - - response = await call.read() - self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) - self.assertFalse(call.cancel()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - - async def test_early_cancel_unary_stream(self): - """Test cancellation before receiving messages.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertFalse(call.cancel()) - - with self.assertRaises(asyncio.CancelledError): - await call.read() - - self.assertTrue(call.cancelled()) - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await - call.details()) - - async def test_late_cancel_unary_stream(self): - """Test cancellation after received all messages.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - for _ in range(_NUM_STREAM_RESPONSES): - response = await call.read() - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # After all messages received, it is possible that the final state - # is received or on its way. It's basically a data race, so our - # expectation here is do not crash :) - call.cancel() - self.assertIn(await call.code(), - [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) - - async def test_too_many_reads_unary_stream(self): - """Test calling read after received all messages fails.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - for _ in range(_NUM_STREAM_RESPONSES): - response = await call.read() - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - self.assertIs(await call.read(), aio.EOF) - - # After the RPC is finished, further reads will lead to exception. - self.assertEqual(await call.code(), grpc.StatusCode.OK) - self.assertIs(await call.read(), aio.EOF) - - async def test_unary_stream_async_generator(self): - """Sunny day test case for unary_stream.""" - # Prepares the request - request = messages_pb2.StreamingOutputCallRequest() - for _ in range(_NUM_STREAM_RESPONSES): - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - self.assertFalse(call.cancelled()) - - async for response in call: - self.assertIs(type(response), - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - - async def test_cancel_unary_stream_in_task_using_read(self): - coro_started = asyncio.Event() - - # Configs the server method to block forever - request = messages_pb2.StreamingOutputCallRequest() - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_INFINITE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - async def another_coro(): - coro_started.set() - await call.read() - - task = self.loop.create_task(another_coro()) - await coro_started.wait() - - self.assertFalse(task.done()) - task.cancel() - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - with self.assertRaises(asyncio.CancelledError): - await task - - async def test_cancel_unary_stream_in_task_using_async_for(self): - coro_started = asyncio.Event() - - # Configs the server method to block forever - request = messages_pb2.StreamingOutputCallRequest() - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_INFINITE_INTERVAL_US, - )) - - # Invokes the actual RPC - call = self._stub.StreamingOutputCall(request) - - async def another_coro(): - coro_started.set() - async for _ in call: - pass - - task = self.loop.create_task(another_coro()) - await coro_started.wait() - - self.assertFalse(task.done()) - task.cancel() - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# def __call__(self, context, callback): +# signature = context.method_name[::-1] +# callback((("test", signature),), None) - with self.assertRaises(asyncio.CancelledError): - await task +# async def coro(): +# server_target, _ = await start_test_server(secure=False) # pylint: disable=unused-variable - def test_call_credentials(self): +# async with aio.insecure_channel(server_target) as channel: +# hi = channel.unary_unary('/grpc.testing.TestService/UnaryCall', +# request_serializer=messages_pb2. +# SimpleRequest.SerializeToString, +# response_deserializer=messages_pb2. +# SimpleResponse.FromString) +# call_credentials = grpc.metadata_call_credentials(DummyAuth()) +# call = hi(messages_pb2.SimpleRequest(), +# credentials=call_credentials) +# response = await call - class DummyAuth(grpc.AuthMetadataPlugin): +# self.assertIsInstance(response, messages_pb2.SimpleResponse) +# self.assertEqual(await call.code(), grpc.StatusCode.OK) - def __call__(self, context, callback): - signature = context.method_name[::-1] - callback((("test", signature),), None) +# self.loop.run_until_complete(coro()) - async def coro(): - server_target, _ = await start_test_server(secure=False) # pylint: disable=unused-variable +# async def test_time_remaining(self): +# request = messages_pb2.StreamingOutputCallRequest() +# # First message comes back immediately +# request.response_parameters.append( +# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) +# # Second message comes back after a unit of wait time +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) - async with aio.insecure_channel(server_target) as channel: - hi = channel.unary_unary('/grpc.testing.TestService/UnaryCall', - request_serializer=messages_pb2. - SimpleRequest.SerializeToString, - response_deserializer=messages_pb2. - SimpleResponse.FromString) - call_credentials = grpc.metadata_call_credentials(DummyAuth()) - call = hi(messages_pb2.SimpleRequest(), - credentials=call_credentials) - response = await call +# call = self._stub.StreamingOutputCall( +# request, timeout=test_constants.SHORT_TIMEOUT * 2) - self.assertIsInstance(response, messages_pb2.SimpleResponse) - self.assertEqual(await call.code(), grpc.StatusCode.OK) +# response = await call.read() +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - self.loop.run_until_complete(coro()) +# # Should be around the same as the timeout +# remained_time = call.time_remaining() +# self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) +# self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 2) - async def test_time_remaining(self): - request = messages_pb2.StreamingOutputCallRequest() - # First message comes back immediately - request.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - # Second message comes back after a unit of wait time - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) +# response = await call.read() +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - call = self._stub.StreamingOutputCall( - request, timeout=test_constants.SHORT_TIMEOUT * 2) +# # Should be around the timeout minus a unit of wait time +# remained_time = call.time_remaining() +# self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT // 2) +# self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) - response = await call.read() - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# self.assertEqual(grpc.StatusCode.OK, await call.code()) - # Should be around the same as the timeout - remained_time = call.time_remaining() - self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) - self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 2) +# class TestStreamUnaryCall(_MulticallableTestMixin, AioTestBase): - response = await call.read() - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# async def test_cancel_stream_unary(self): +# call = self._stub.StreamingInputCall() - # Should be around the timeout minus a unit of wait time - remained_time = call.time_remaining() - self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT // 2) - self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) +# # Prepares the request +# payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) +# request = messages_pb2.StreamingInputCallRequest(payload=payload) - self.assertEqual(grpc.StatusCode.OK, await call.code()) +# # Sends out requests +# for _ in range(_NUM_STREAM_RESPONSES): +# await call.write(request) +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) -class TestStreamUnaryCall(_MulticallableTestMixin, AioTestBase): +# await call.done_writing() - async def test_cancel_stream_unary(self): - call = self._stub.StreamingInputCall() +# with self.assertRaises(asyncio.CancelledError): +# await call - # Prepares the request - payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) - request = messages_pb2.StreamingInputCallRequest(payload=payload) +# async def test_early_cancel_stream_unary(self): +# call = self._stub.StreamingInputCall() - # Sends out requests - for _ in range(_NUM_STREAM_RESPONSES): - await call.write(request) +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) +# with self.assertRaises(asyncio.InvalidStateError): +# await call.write(messages_pb2.StreamingInputCallRequest()) - await call.done_writing() +# # Should be no-op +# await call.done_writing() - with self.assertRaises(asyncio.CancelledError): - await call +# with self.assertRaises(asyncio.CancelledError): +# await call - async def test_early_cancel_stream_unary(self): - call = self._stub.StreamingInputCall() +# async def test_write_after_done_writing(self): +# call = self._stub.StreamingInputCall() + +# # Prepares the request +# payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) +# request = messages_pb2.StreamingInputCallRequest(payload=payload) + +# # Sends out requests +# for _ in range(_NUM_STREAM_RESPONSES): +# await call.write(request) + +# # Should be no-op +# await call.done_writing() + +# with self.assertRaises(asyncio.InvalidStateError): +# await call.write(messages_pb2.StreamingInputCallRequest()) - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) +# response = await call +# self.assertIsInstance(response, messages_pb2.StreamingInputCallResponse) +# self.assertEqual(_NUM_STREAM_RESPONSES * _REQUEST_PAYLOAD_SIZE, +# response.aggregated_payload_size) - with self.assertRaises(asyncio.InvalidStateError): - await call.write(messages_pb2.StreamingInputCallRequest()) +# self.assertEqual(await call.code(), grpc.StatusCode.OK) + +# async def test_error_in_async_generator(self): +# # Server will pause between responses +# request = messages_pb2.StreamingOutputCallRequest() +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) - # Should be no-op - await call.done_writing() +# # We expect the request iterator to receive the exception +# request_iterator_received_the_exception = asyncio.Event() - with self.assertRaises(asyncio.CancelledError): - await call +# async def request_iterator(): +# with self.assertRaises(asyncio.CancelledError): +# for _ in range(_NUM_STREAM_RESPONSES): +# yield request +# await asyncio.sleep(test_constants.SHORT_TIMEOUT) +# request_iterator_received_the_exception.set() - async def test_write_after_done_writing(self): - call = self._stub.StreamingInputCall() +# call = self._stub.StreamingInputCall(request_iterator()) + +# # Cancel the RPC after at least one response +# async def cancel_later(): +# await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) +# call.cancel() + +# cancel_later_task = self.loop.create_task(cancel_later()) + +# # No exceptions here +# with self.assertRaises(asyncio.CancelledError): +# await call - # Prepares the request - payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) - request = messages_pb2.StreamingInputCallRequest(payload=payload) +# await request_iterator_received_the_exception.wait() - # Sends out requests - for _ in range(_NUM_STREAM_RESPONSES): - await call.write(request) - - # Should be no-op - await call.done_writing() - - with self.assertRaises(asyncio.InvalidStateError): - await call.write(messages_pb2.StreamingInputCallRequest()) - - response = await call - self.assertIsInstance(response, messages_pb2.StreamingInputCallResponse) - self.assertEqual(_NUM_STREAM_RESPONSES * _REQUEST_PAYLOAD_SIZE, - response.aggregated_payload_size) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - - async def test_error_in_async_generator(self): - # Server will pause between responses - request = messages_pb2.StreamingOutputCallRequest() - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # We expect the request iterator to receive the exception - request_iterator_received_the_exception = asyncio.Event() - - async def request_iterator(): - with self.assertRaises(asyncio.CancelledError): - for _ in range(_NUM_STREAM_RESPONSES): - yield request - await asyncio.sleep(test_constants.SHORT_TIMEOUT) - request_iterator_received_the_exception.set() - - call = self._stub.StreamingInputCall(request_iterator()) - - # Cancel the RPC after at least one response - async def cancel_later(): - await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) - call.cancel() - - cancel_later_task = self.loop.create_task(cancel_later()) - - # No exceptions here - with self.assertRaises(asyncio.CancelledError): - await call - - await request_iterator_received_the_exception.wait() - - # No failures in the cancel later task! - await cancel_later_task - - -# Prepares the request that stream in a ping-pong manner. -_STREAM_OUTPUT_REQUEST_ONE_RESPONSE = messages_pb2.StreamingOutputCallRequest() -_STREAM_OUTPUT_REQUEST_ONE_RESPONSE.response_parameters.append( - messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE)) - - -class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): - - async def test_cancel(self): - # Invokes the actual RPC - call = self._stub.FullDuplexCall() - - for _ in range(_NUM_STREAM_RESPONSES): - await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - response = await call.read() - self.assertIsInstance(response, - messages_pb2.StreamingOutputCallResponse) - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - async def test_cancel_with_pending_read(self): - call = self._stub.FullDuplexCall() - - await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - async def test_cancel_with_ongoing_read(self): - call = self._stub.FullDuplexCall() - coro_started = asyncio.Event() - - async def read_coro(): - coro_started.set() - await call.read() - - read_task = self.loop.create_task(read_coro()) - await coro_started.wait() - self.assertFalse(read_task.done()) - - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - async def test_early_cancel(self): - call = self._stub.FullDuplexCall() - - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - async def test_cancel_after_done_writing(self): - call = self._stub.FullDuplexCall() - await call.done_writing() - - # Cancels the RPC - self.assertFalse(call.done()) - self.assertFalse(call.cancelled()) - self.assertTrue(call.cancel()) - self.assertTrue(call.cancelled()) - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - - async def test_late_cancel(self): - call = self._stub.FullDuplexCall() - await call.done_writing() - self.assertEqual(grpc.StatusCode.OK, await call.code()) - - # Cancels the RPC - self.assertTrue(call.done()) - self.assertFalse(call.cancelled()) - self.assertFalse(call.cancel()) - self.assertFalse(call.cancelled()) - - # Status is still OK - self.assertEqual(grpc.StatusCode.OK, await call.code()) - - async def test_async_generator(self): - - async def request_generator(): - yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE - yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE - - call = self._stub.FullDuplexCall(request_generator()) - async for response in call: - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - - async def test_too_many_reads(self): - - async def request_generator(): - for _ in range(_NUM_STREAM_RESPONSES): - yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE - - call = self._stub.FullDuplexCall(request_generator()) - for _ in range(_NUM_STREAM_RESPONSES): - response = await call.read() - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - self.assertIs(await call.read(), aio.EOF) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - # After the RPC finished, the read should also produce EOF - self.assertIs(await call.read(), aio.EOF) - - async def test_read_write_after_done_writing(self): - call = self._stub.FullDuplexCall() - - # Writes two requests, and pending two requests - await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - await call.done_writing() - - # Further write should fail - with self.assertRaises(asyncio.InvalidStateError): - await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - - # But read should be unaffected - response = await call.read() - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - response = await call.read() - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - self.assertEqual(await call.code(), grpc.StatusCode.OK) - - async def test_error_in_async_generator(self): - # Server will pause between responses - request = messages_pb2.StreamingOutputCallRequest() - request.response_parameters.append( - messages_pb2.ResponseParameters( - size=_RESPONSE_PAYLOAD_SIZE, - interval_us=_RESPONSE_INTERVAL_US, - )) - - # We expect the request iterator to receive the exception - request_iterator_received_the_exception = asyncio.Event() - - async def request_iterator(): - with self.assertRaises(asyncio.CancelledError): - for _ in range(_NUM_STREAM_RESPONSES): - yield request - await asyncio.sleep(test_constants.SHORT_TIMEOUT) - request_iterator_received_the_exception.set() - - call = self._stub.FullDuplexCall(request_iterator()) - - # Cancel the RPC after at least one response - async def cancel_later(): - await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) - call.cancel() - - cancel_later_task = self.loop.create_task(cancel_later()) - - # No exceptions here - async for response in call: - self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - - await request_iterator_received_the_exception.wait() - - self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - # No failures in the cancel later task! - await cancel_later_task +# # No failures in the cancel later task! +# await cancel_later_task + +# # Prepares the request that stream in a ping-pong manner. +# _STREAM_OUTPUT_REQUEST_ONE_RESPONSE = messages_pb2.StreamingOutputCallRequest() +# _STREAM_OUTPUT_REQUEST_ONE_RESPONSE.response_parameters.append( +# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE)) +# class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): + +# async def test_cancel(self): +# # Invokes the actual RPC +# call = self._stub.FullDuplexCall() + +# for _ in range(_NUM_STREAM_RESPONSES): +# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) +# response = await call.read() +# self.assertIsInstance(response, +# messages_pb2.StreamingOutputCallResponse) +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# async def test_cancel_with_pending_read(self): +# call = self._stub.FullDuplexCall() + +# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# async def test_cancel_with_ongoing_read(self): +# call = self._stub.FullDuplexCall() +# coro_started = asyncio.Event() + +# async def read_coro(): +# coro_started.set() +# await call.read() + +# read_task = self.loop.create_task(read_coro()) +# await coro_started.wait() +# self.assertFalse(read_task.done()) + +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# async def test_early_cancel(self): +# call = self._stub.FullDuplexCall() + +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# async def test_cancel_after_done_writing(self): +# call = self._stub.FullDuplexCall() +# await call.done_writing() + +# # Cancels the RPC +# self.assertFalse(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertTrue(call.cancel()) +# self.assertTrue(call.cancelled()) +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + +# async def test_late_cancel(self): +# call = self._stub.FullDuplexCall() +# await call.done_writing() +# self.assertEqual(grpc.StatusCode.OK, await call.code()) + +# # Cancels the RPC +# self.assertTrue(call.done()) +# self.assertFalse(call.cancelled()) +# self.assertFalse(call.cancel()) +# self.assertFalse(call.cancelled()) + +# # Status is still OK +# self.assertEqual(grpc.StatusCode.OK, await call.code()) + +# async def test_async_generator(self): + +# async def request_generator(): +# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE +# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE + +# call = self._stub.FullDuplexCall(request_generator()) +# async for response in call: +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# self.assertEqual(await call.code(), grpc.StatusCode.OK) + +# async def test_too_many_reads(self): + +# async def request_generator(): +# for _ in range(_NUM_STREAM_RESPONSES): +# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE + +# call = self._stub.FullDuplexCall(request_generator()) +# for _ in range(_NUM_STREAM_RESPONSES): +# response = await call.read() +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# self.assertIs(await call.read(), aio.EOF) + +# self.assertEqual(await call.code(), grpc.StatusCode.OK) +# # After the RPC finished, the read should also produce EOF +# self.assertIs(await call.read(), aio.EOF) + +# async def test_read_write_after_done_writing(self): +# call = self._stub.FullDuplexCall() + +# # Writes two requests, and pending two requests +# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) +# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) +# await call.done_writing() + +# # Further write should fail +# with self.assertRaises(asyncio.InvalidStateError): +# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + +# # But read should be unaffected +# response = await call.read() +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) +# response = await call.read() +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# self.assertEqual(await call.code(), grpc.StatusCode.OK) + +# async def test_error_in_async_generator(self): +# # Server will pause between responses +# request = messages_pb2.StreamingOutputCallRequest() +# request.response_parameters.append( +# messages_pb2.ResponseParameters( +# size=_RESPONSE_PAYLOAD_SIZE, +# interval_us=_RESPONSE_INTERVAL_US, +# )) + +# # We expect the request iterator to receive the exception +# request_iterator_received_the_exception = asyncio.Event() + +# async def request_iterator(): +# with self.assertRaises(asyncio.CancelledError): +# for _ in range(_NUM_STREAM_RESPONSES): +# yield request +# await asyncio.sleep(test_constants.SHORT_TIMEOUT) +# request_iterator_received_the_exception.set() + +# call = self._stub.FullDuplexCall(request_iterator()) + +# # Cancel the RPC after at least one response +# async def cancel_later(): +# await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) +# call.cancel() + +# cancel_later_task = self.loop.create_task(cancel_later()) + +# # No exceptions here +# async for response in call: +# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + +# await request_iterator_received_the_exception.wait() + +# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) +# # No failures in the cancel later task! +# await cancel_later_task if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) From 7b7c0ba5fc40085340ceb4070ac9f6a489e0213b Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 16:15:13 -0800 Subject: [PATCH 08/14] Fix the SEGFAULT caused by initialization ordering --- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 20 +- .../_cygrpc/aio/iomgr/resolver.pyx.pxi | 3 - .../grpcio_tests/tests_aio/unit/call_test.py | 1216 +++++++++-------- 3 files changed, 624 insertions(+), 615 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index fe36a4e63aa..27a37dfe053 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -54,15 +54,18 @@ def init_grpc_aio(): _grpc_aio_loop = asyncio.get_event_loop() _event_loop_thread_ident = threading.current_thread().ident - # TODO(https://github.com/grpc/grpc/issues/22244) we need a the - # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC - # library won't shutdown cleanly. - grpc_init() - if grpc_aio_engine is AsyncIOEngine.CUSTOM_IO_MANAGER: - # Activates asyncio IO manager + # Activates asyncio IO manager. + # NOTE(lidiz) Custom IO manager must be activated before the first + # `grpc_init()`. Otherwise, some special configurations in Core won't + # pick up the change, and resulted in SEGFAULT or ABORT. install_asyncio_iomgr() + # TODO(https://github.com/grpc/grpc/issues/22244) we need a the + # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC + # library won't shutdown cleanly. + grpc_init() + # Timers are triggered by the Asyncio loop. We disable # the background thread that is being used by the native # gRPC iomgr. @@ -71,6 +74,11 @@ def init_grpc_aio(): # gRPC callbaks are executed within the same thread used by the Asyncio # event loop, as it is being done by the other Asyncio callbacks. Executor.SetThreadingAll(False) + else: + # TODO(https://github.com/grpc/grpc/issues/22244) we need a the + # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC + # library won't shutdown cleanly. + grpc_init() _grpc_aio_initialized = False diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi index 7897368bc83..7d47fa77b00 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/resolver.pyx.pxi @@ -32,9 +32,7 @@ cdef class _AsyncioResolver: async def _async_resolve(self, bytes host, bytes port): self._task_resolve = None try: - _LOGGER.debug('_AsyncioResolver before') resolved = await grpc_aio_loop().getaddrinfo(host, port) - _LOGGER.debug('_AsyncioResolver after') except Exception as e: grpc_custom_resolve_callback( self._grpc_resolver, @@ -52,7 +50,6 @@ cdef class _AsyncioResolver: cdef void resolve(self, char* host, char* port): assert not self._task_resolve - _LOGGER.debug('_AsyncioResolver resolve') self._task_resolve = grpc_aio_loop().create_task( self._async_resolve(host, port) ) diff --git a/src/python/grpcio_tests/tests_aio/unit/call_test.py b/src/python/grpcio_tests/tests_aio/unit/call_test.py index a9ff5f5dca8..e47c00a62ab 100644 --- a/src/python/grpcio_tests/tests_aio/unit/call_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/call_test.py @@ -49,32 +49,32 @@ class _MulticallableTestMixin(): class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): - # async def test_call_to_string(self): - # call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_to_string(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - # self.assertTrue(str(call) is not None) - # self.assertTrue(repr(call) is not None) + self.assertTrue(str(call) is not None) + self.assertTrue(repr(call) is not None) - # response = await call + response = await call - # self.assertTrue(str(call) is not None) - # self.assertTrue(repr(call) is not None) + self.assertTrue(str(call) is not None) + self.assertTrue(repr(call) is not None) - # async def test_call_ok(self): - # call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def test_call_ok(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) - # self.assertFalse(call.done()) + self.assertFalse(call.done()) - # response = await call + response = await call - # self.assertTrue(call.done()) - # self.assertIsInstance(response, messages_pb2.SimpleResponse) - # self.assertEqual(await call.code(), grpc.StatusCode.OK) + self.assertTrue(call.done()) + self.assertIsInstance(response, messages_pb2.SimpleResponse) + self.assertEqual(await call.code(), grpc.StatusCode.OK) - # # Response is cached at call object level, reentrance - # # returns again the same response - # response_retry = await call - # self.assertIs(response, response_retry) + # Response is cached at call object level, reentrance + # returns again the same response + response_retry = await call + self.assertIs(response, response_retry) async def test_call_rpc_error(self): async with aio.insecure_channel(_UNREACHABLE_TARGET) as channel: @@ -91,664 +91,668 @@ class TestUnaryUnaryCall(_MulticallableTestMixin, AioTestBase): self.assertTrue(call.done()) self.assertEqual(grpc.StatusCode.UNAVAILABLE, await call.code()) + async def test_call_code_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual(await call.code(), grpc.StatusCode.OK) -# async def test_call_code_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual(await call.code(), grpc.StatusCode.OK) + async def test_call_details_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual('', await call.details()) -# async def test_call_details_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual('', await call.details()) + async def test_call_initial_metadata_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual((), await call.initial_metadata()) -# async def test_call_initial_metadata_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual((), await call.initial_metadata()) + async def test_call_trailing_metadata_awaitable(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertEqual((), await call.trailing_metadata()) -# async def test_call_trailing_metadata_awaitable(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# self.assertEqual((), await call.trailing_metadata()) + async def test_call_initial_metadata_cancelable(self): + coro_started = asyncio.Event() + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def test_call_initial_metadata_cancelable(self): -# coro_started = asyncio.Event() -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def coro(): + coro_started.set() + await call.initial_metadata() -# async def coro(): -# coro_started.set() -# await call.initial_metadata() + task = self.loop.create_task(coro()) + await coro_started.wait() + task.cancel() -# task = self.loop.create_task(coro()) -# await coro_started.wait() -# task.cancel() + # Test that initial metadata can still be asked thought + # a cancellation happened with the previous task + self.assertEqual((), await call.initial_metadata()) -# # Test that initial metadata can still be asked thought -# # a cancellation happened with the previous task -# self.assertEqual((), await call.initial_metadata()) + async def test_call_initial_metadata_multiple_waiters(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def test_call_initial_metadata_multiple_waiters(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def coro(): + return await call.initial_metadata() -# async def coro(): -# return await call.initial_metadata() + task1 = self.loop.create_task(coro()) + task2 = self.loop.create_task(coro()) -# task1 = self.loop.create_task(coro()) -# task2 = self.loop.create_task(coro()) + await call -# await call + self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) -# self.assertEqual([(), ()], await asyncio.gather(*[task1, task2])) + async def test_call_code_cancelable(self): + coro_started = asyncio.Event() + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def test_call_code_cancelable(self): -# coro_started = asyncio.Event() -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def coro(): + coro_started.set() + await call.code() -# async def coro(): -# coro_started.set() -# await call.code() + task = self.loop.create_task(coro()) + await coro_started.wait() + task.cancel() -# task = self.loop.create_task(coro()) -# await coro_started.wait() -# task.cancel() + # Test that code can still be asked thought + # a cancellation happened with the previous task + self.assertEqual(grpc.StatusCode.OK, await call.code()) -# # Test that code can still be asked thought -# # a cancellation happened with the previous task -# self.assertEqual(grpc.StatusCode.OK, await call.code()) + async def test_call_code_multiple_waiters(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def test_call_code_multiple_waiters(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + async def coro(): + return await call.code() -# async def coro(): -# return await call.code() + task1 = self.loop.create_task(coro()) + task2 = self.loop.create_task(coro()) -# task1 = self.loop.create_task(coro()) -# task2 = self.loop.create_task(coro()) + await call -# await call + self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await + asyncio.gather(task1, task2)) -# self.assertEqual([grpc.StatusCode.OK, grpc.StatusCode.OK], await -# asyncio.gather(task1, task2)) + async def test_cancel_unary_unary(self): + call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) -# async def test_cancel_unary_unary(self): -# call = self._stub.UnaryCall(messages_pb2.SimpleRequest()) + self.assertFalse(call.cancelled()) -# self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) -# self.assertTrue(call.cancel()) -# self.assertFalse(call.cancel()) + with self.assertRaises(asyncio.CancelledError): + await call -# with self.assertRaises(asyncio.CancelledError): -# await call + # The info in the RpcError should match the info in Call object. + self.assertTrue(call.cancelled()) + self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) + self.assertEqual(await call.details(), + 'Locally cancelled by application!') -# # The info in the RpcError should match the info in Call object. -# self.assertTrue(call.cancelled()) -# self.assertEqual(await call.code(), grpc.StatusCode.CANCELLED) -# self.assertEqual(await call.details(), -# 'Locally cancelled by application!') + async def test_cancel_unary_unary_in_task(self): + coro_started = asyncio.Event() + call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) -# async def test_cancel_unary_unary_in_task(self): -# coro_started = asyncio.Event() -# call = self._stub.EmptyCall(messages_pb2.SimpleRequest()) + async def another_coro(): + coro_started.set() + await call -# async def another_coro(): -# coro_started.set() -# await call + task = self.loop.create_task(another_coro()) + await coro_started.wait() -# task = self.loop.create_task(another_coro()) -# await coro_started.wait() + self.assertFalse(task.done()) + task.cancel() -# self.assertFalse(task.done()) -# task.cancel() + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + with self.assertRaises(asyncio.CancelledError): + await task -# with self.assertRaises(asyncio.CancelledError): -# await task -# class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): +class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase): -# async def test_cancel_unary_stream(self): -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) -# self.assertFalse(call.cancelled()) - -# response = await call.read() -# self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# self.assertTrue(call.cancel()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await -# call.details()) -# self.assertFalse(call.cancel()) - -# with self.assertRaises(asyncio.CancelledError): -# await call.read() -# self.assertTrue(call.cancelled()) - -# async def test_multiple_cancel_unary_stream(self): -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) -# self.assertFalse(call.cancelled()) - -# response = await call.read() -# self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# self.assertTrue(call.cancel()) -# self.assertFalse(call.cancel()) -# self.assertFalse(call.cancel()) -# self.assertFalse(call.cancel()) - -# with self.assertRaises(asyncio.CancelledError): -# await call.read() - -# async def test_early_cancel_unary_stream(self): -# """Test cancellation before receiving messages.""" -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) - -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertFalse(call.cancel()) - -# with self.assertRaises(asyncio.CancelledError): -# await call.read() - -# self.assertTrue(call.cancelled()) - -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await -# call.details()) - -# async def test_late_cancel_unary_stream(self): -# """Test cancellation after received all messages.""" -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) - -# for _ in range(_NUM_STREAM_RESPONSES): -# response = await call.read() -# self.assertIs(type(response), -# messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# # After all messages received, it is possible that the final state -# # is received or on its way. It's basically a data race, so our -# # expectation here is do not crash :) -# call.cancel() -# self.assertIn(await call.code(), -# [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) - -# async def test_too_many_reads_unary_stream(self): -# """Test calling read after received all messages fails.""" -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) - -# for _ in range(_NUM_STREAM_RESPONSES): -# response = await call.read() -# self.assertIs(type(response), -# messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# self.assertIs(await call.read(), aio.EOF) - -# # After the RPC is finished, further reads will lead to exception. -# self.assertEqual(await call.code(), grpc.StatusCode.OK) -# self.assertIs(await call.read(), aio.EOF) - -# async def test_unary_stream_async_generator(self): -# """Sunny day test case for unary_stream.""" -# # Prepares the request -# request = messages_pb2.StreamingOutputCallRequest() -# for _ in range(_NUM_STREAM_RESPONSES): -# request.response_parameters.append( -# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) -# self.assertFalse(call.cancelled()) - -# async for response in call: -# self.assertIs(type(response), -# messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# self.assertEqual(await call.code(), grpc.StatusCode.OK) - -# async def test_cancel_unary_stream_in_task_using_read(self): -# coro_started = asyncio.Event() - -# # Configs the server method to block forever -# request = messages_pb2.StreamingOutputCallRequest() -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_INFINITE_INTERVAL_US, -# )) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) - -# async def another_coro(): -# coro_started.set() -# await call.read() - -# task = self.loop.create_task(another_coro()) -# await coro_started.wait() - -# self.assertFalse(task.done()) -# task.cancel() - -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# with self.assertRaises(asyncio.CancelledError): -# await task - -# async def test_cancel_unary_stream_in_task_using_async_for(self): -# coro_started = asyncio.Event() - -# # Configs the server method to block forever -# request = messages_pb2.StreamingOutputCallRequest() -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_INFINITE_INTERVAL_US, -# )) - -# # Invokes the actual RPC -# call = self._stub.StreamingOutputCall(request) - -# async def another_coro(): -# coro_started.set() -# async for _ in call: -# pass - -# task = self.loop.create_task(another_coro()) -# await coro_started.wait() - -# self.assertFalse(task.done()) -# task.cancel() - -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# with self.assertRaises(asyncio.CancelledError): -# await task + async def test_cancel_unary_stream(self): + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) -# def test_call_credentials(self): + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) -# class DummyAuth(grpc.AuthMetadataPlugin): + response = await call.read() + self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# def __call__(self, context, callback): -# signature = context.method_name[::-1] -# callback((("test", signature),), None) + self.assertTrue(call.cancel()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + call.details()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + self.assertTrue(call.cancelled()) + + async def test_multiple_cancel_unary_stream(self): + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) + + response = await call.read() + self.assertIs(type(response), messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) + self.assertFalse(call.cancel()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + + async def test_early_cancel_unary_stream(self): + """Test cancellation before receiving messages.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertFalse(call.cancel()) + + with self.assertRaises(asyncio.CancelledError): + await call.read() + + self.assertTrue(call.cancelled()) + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + self.assertEqual(_LOCAL_CANCEL_DETAILS_EXPECTATION, await + call.details()) + + async def test_late_cancel_unary_stream(self): + """Test cancellation after received all messages.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + for _ in range(_NUM_STREAM_RESPONSES): + response = await call.read() + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # After all messages received, it is possible that the final state + # is received or on its way. It's basically a data race, so our + # expectation here is do not crash :) + call.cancel() + self.assertIn(await call.code(), + [grpc.StatusCode.OK, grpc.StatusCode.CANCELLED]) + + async def test_too_many_reads_unary_stream(self): + """Test calling read after received all messages fails.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + for _ in range(_NUM_STREAM_RESPONSES): + response = await call.read() + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + self.assertIs(await call.read(), aio.EOF) + + # After the RPC is finished, further reads will lead to exception. + self.assertEqual(await call.code(), grpc.StatusCode.OK) + self.assertIs(await call.read(), aio.EOF) + + async def test_unary_stream_async_generator(self): + """Sunny day test case for unary_stream.""" + # Prepares the request + request = messages_pb2.StreamingOutputCallRequest() + for _ in range(_NUM_STREAM_RESPONSES): + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + self.assertFalse(call.cancelled()) + + async for response in call: + self.assertIs(type(response), + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + + async def test_cancel_unary_stream_in_task_using_read(self): + coro_started = asyncio.Event() + + # Configs the server method to block forever + request = messages_pb2.StreamingOutputCallRequest() + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_INFINITE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + async def another_coro(): + coro_started.set() + await call.read() + + task = self.loop.create_task(another_coro()) + await coro_started.wait() + + self.assertFalse(task.done()) + task.cancel() + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + with self.assertRaises(asyncio.CancelledError): + await task + + async def test_cancel_unary_stream_in_task_using_async_for(self): + coro_started = asyncio.Event() + + # Configs the server method to block forever + request = messages_pb2.StreamingOutputCallRequest() + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_INFINITE_INTERVAL_US, + )) + + # Invokes the actual RPC + call = self._stub.StreamingOutputCall(request) + + async def another_coro(): + coro_started.set() + async for _ in call: + pass + + task = self.loop.create_task(another_coro()) + await coro_started.wait() + + self.assertFalse(task.done()) + task.cancel() + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# async def coro(): -# server_target, _ = await start_test_server(secure=False) # pylint: disable=unused-variable + with self.assertRaises(asyncio.CancelledError): + await task -# async with aio.insecure_channel(server_target) as channel: -# hi = channel.unary_unary('/grpc.testing.TestService/UnaryCall', -# request_serializer=messages_pb2. -# SimpleRequest.SerializeToString, -# response_deserializer=messages_pb2. -# SimpleResponse.FromString) -# call_credentials = grpc.metadata_call_credentials(DummyAuth()) -# call = hi(messages_pb2.SimpleRequest(), -# credentials=call_credentials) -# response = await call + def test_call_credentials(self): -# self.assertIsInstance(response, messages_pb2.SimpleResponse) -# self.assertEqual(await call.code(), grpc.StatusCode.OK) + class DummyAuth(grpc.AuthMetadataPlugin): -# self.loop.run_until_complete(coro()) + def __call__(self, context, callback): + signature = context.method_name[::-1] + callback((("test", signature),), None) -# async def test_time_remaining(self): -# request = messages_pb2.StreamingOutputCallRequest() -# # First message comes back immediately -# request.response_parameters.append( -# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) -# # Second message comes back after a unit of wait time -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) + async def coro(): + server_target, _ = await start_test_server(secure=False) # pylint: disable=unused-variable -# call = self._stub.StreamingOutputCall( -# request, timeout=test_constants.SHORT_TIMEOUT * 2) + async with aio.insecure_channel(server_target) as channel: + hi = channel.unary_unary('/grpc.testing.TestService/UnaryCall', + request_serializer=messages_pb2. + SimpleRequest.SerializeToString, + response_deserializer=messages_pb2. + SimpleResponse.FromString) + call_credentials = grpc.metadata_call_credentials(DummyAuth()) + call = hi(messages_pb2.SimpleRequest(), + credentials=call_credentials) + response = await call -# response = await call.read() -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + self.assertIsInstance(response, messages_pb2.SimpleResponse) + self.assertEqual(await call.code(), grpc.StatusCode.OK) -# # Should be around the same as the timeout -# remained_time = call.time_remaining() -# self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) -# self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 2) + self.loop.run_until_complete(coro()) -# response = await call.read() -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + async def test_time_remaining(self): + request = messages_pb2.StreamingOutputCallRequest() + # First message comes back immediately + request.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE,)) + # Second message comes back after a unit of wait time + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) -# # Should be around the timeout minus a unit of wait time -# remained_time = call.time_remaining() -# self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT // 2) -# self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) + call = self._stub.StreamingOutputCall( + request, timeout=test_constants.SHORT_TIMEOUT * 2) -# self.assertEqual(grpc.StatusCode.OK, await call.code()) + response = await call.read() + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# class TestStreamUnaryCall(_MulticallableTestMixin, AioTestBase): + # Should be around the same as the timeout + remained_time = call.time_remaining() + self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) + self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 2) -# async def test_cancel_stream_unary(self): -# call = self._stub.StreamingInputCall() + response = await call.read() + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# # Prepares the request -# payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) -# request = messages_pb2.StreamingInputCallRequest(payload=payload) + # Should be around the timeout minus a unit of wait time + remained_time = call.time_remaining() + self.assertGreater(remained_time, test_constants.SHORT_TIMEOUT // 2) + self.assertLess(remained_time, test_constants.SHORT_TIMEOUT * 3 // 2) -# # Sends out requests -# for _ in range(_NUM_STREAM_RESPONSES): -# await call.write(request) + self.assertEqual(grpc.StatusCode.OK, await call.code()) -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# await call.done_writing() +class TestStreamUnaryCall(_MulticallableTestMixin, AioTestBase): -# with self.assertRaises(asyncio.CancelledError): -# await call + async def test_cancel_stream_unary(self): + call = self._stub.StreamingInputCall() -# async def test_early_cancel_stream_unary(self): -# call = self._stub.StreamingInputCall() + # Prepares the request + payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) + request = messages_pb2.StreamingInputCallRequest(payload=payload) -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) + # Sends out requests + for _ in range(_NUM_STREAM_RESPONSES): + await call.write(request) -# with self.assertRaises(asyncio.InvalidStateError): -# await call.write(messages_pb2.StreamingInputCallRequest()) + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) -# # Should be no-op -# await call.done_writing() + await call.done_writing() -# with self.assertRaises(asyncio.CancelledError): -# await call + with self.assertRaises(asyncio.CancelledError): + await call -# async def test_write_after_done_writing(self): -# call = self._stub.StreamingInputCall() - -# # Prepares the request -# payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) -# request = messages_pb2.StreamingInputCallRequest(payload=payload) - -# # Sends out requests -# for _ in range(_NUM_STREAM_RESPONSES): -# await call.write(request) - -# # Should be no-op -# await call.done_writing() - -# with self.assertRaises(asyncio.InvalidStateError): -# await call.write(messages_pb2.StreamingInputCallRequest()) + async def test_early_cancel_stream_unary(self): + call = self._stub.StreamingInputCall() -# response = await call -# self.assertIsInstance(response, messages_pb2.StreamingInputCallResponse) -# self.assertEqual(_NUM_STREAM_RESPONSES * _REQUEST_PAYLOAD_SIZE, -# response.aggregated_payload_size) + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) -# self.assertEqual(await call.code(), grpc.StatusCode.OK) - -# async def test_error_in_async_generator(self): -# # Server will pause between responses -# request = messages_pb2.StreamingOutputCallRequest() -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) + with self.assertRaises(asyncio.InvalidStateError): + await call.write(messages_pb2.StreamingInputCallRequest()) -# # We expect the request iterator to receive the exception -# request_iterator_received_the_exception = asyncio.Event() + # Should be no-op + await call.done_writing() -# async def request_iterator(): -# with self.assertRaises(asyncio.CancelledError): -# for _ in range(_NUM_STREAM_RESPONSES): -# yield request -# await asyncio.sleep(test_constants.SHORT_TIMEOUT) -# request_iterator_received_the_exception.set() + with self.assertRaises(asyncio.CancelledError): + await call -# call = self._stub.StreamingInputCall(request_iterator()) - -# # Cancel the RPC after at least one response -# async def cancel_later(): -# await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) -# call.cancel() - -# cancel_later_task = self.loop.create_task(cancel_later()) - -# # No exceptions here -# with self.assertRaises(asyncio.CancelledError): -# await call + async def test_write_after_done_writing(self): + call = self._stub.StreamingInputCall() -# await request_iterator_received_the_exception.wait() + # Prepares the request + payload = messages_pb2.Payload(body=b'\0' * _REQUEST_PAYLOAD_SIZE) + request = messages_pb2.StreamingInputCallRequest(payload=payload) -# # No failures in the cancel later task! -# await cancel_later_task - -# # Prepares the request that stream in a ping-pong manner. -# _STREAM_OUTPUT_REQUEST_ONE_RESPONSE = messages_pb2.StreamingOutputCallRequest() -# _STREAM_OUTPUT_REQUEST_ONE_RESPONSE.response_parameters.append( -# messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE)) + # Sends out requests + for _ in range(_NUM_STREAM_RESPONSES): + await call.write(request) + + # Should be no-op + await call.done_writing() + + with self.assertRaises(asyncio.InvalidStateError): + await call.write(messages_pb2.StreamingInputCallRequest()) + + response = await call + self.assertIsInstance(response, messages_pb2.StreamingInputCallResponse) + self.assertEqual(_NUM_STREAM_RESPONSES * _REQUEST_PAYLOAD_SIZE, + response.aggregated_payload_size) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + + async def test_error_in_async_generator(self): + # Server will pause between responses + request = messages_pb2.StreamingOutputCallRequest() + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # We expect the request iterator to receive the exception + request_iterator_received_the_exception = asyncio.Event() + + async def request_iterator(): + with self.assertRaises(asyncio.CancelledError): + for _ in range(_NUM_STREAM_RESPONSES): + yield request + await asyncio.sleep(test_constants.SHORT_TIMEOUT) + request_iterator_received_the_exception.set() + + call = self._stub.StreamingInputCall(request_iterator()) + + # Cancel the RPC after at least one response + async def cancel_later(): + await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) + call.cancel() + + cancel_later_task = self.loop.create_task(cancel_later()) + + # No exceptions here + with self.assertRaises(asyncio.CancelledError): + await call + + await request_iterator_received_the_exception.wait() + + # No failures in the cancel later task! + await cancel_later_task + + +# Prepares the request that stream in a ping-pong manner. +_STREAM_OUTPUT_REQUEST_ONE_RESPONSE = messages_pb2.StreamingOutputCallRequest() +_STREAM_OUTPUT_REQUEST_ONE_RESPONSE.response_parameters.append( + messages_pb2.ResponseParameters(size=_RESPONSE_PAYLOAD_SIZE)) + + +class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): + + async def test_cancel(self): + # Invokes the actual RPC + call = self._stub.FullDuplexCall() + + for _ in range(_NUM_STREAM_RESPONSES): + await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + response = await call.read() + self.assertIsInstance(response, + messages_pb2.StreamingOutputCallResponse) + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + async def test_cancel_with_pending_read(self): + call = self._stub.FullDuplexCall() + + await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + async def test_cancel_with_ongoing_read(self): + call = self._stub.FullDuplexCall() + coro_started = asyncio.Event() + + async def read_coro(): + coro_started.set() + await call.read() + + read_task = self.loop.create_task(read_coro()) + await coro_started.wait() + self.assertFalse(read_task.done()) + + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + async def test_early_cancel(self): + call = self._stub.FullDuplexCall() + + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + async def test_cancel_after_done_writing(self): + call = self._stub.FullDuplexCall() + await call.done_writing() + + # Cancels the RPC + self.assertFalse(call.done()) + self.assertFalse(call.cancelled()) + self.assertTrue(call.cancel()) + self.assertTrue(call.cancelled()) + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + + async def test_late_cancel(self): + call = self._stub.FullDuplexCall() + await call.done_writing() + self.assertEqual(grpc.StatusCode.OK, await call.code()) + + # Cancels the RPC + self.assertTrue(call.done()) + self.assertFalse(call.cancelled()) + self.assertFalse(call.cancel()) + self.assertFalse(call.cancelled()) + + # Status is still OK + self.assertEqual(grpc.StatusCode.OK, await call.code()) + + async def test_async_generator(self): + + async def request_generator(): + yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE + yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE + + call = self._stub.FullDuplexCall(request_generator()) + async for response in call: + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + + async def test_too_many_reads(self): + + async def request_generator(): + for _ in range(_NUM_STREAM_RESPONSES): + yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE + + call = self._stub.FullDuplexCall(request_generator()) + for _ in range(_NUM_STREAM_RESPONSES): + response = await call.read() + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + self.assertIs(await call.read(), aio.EOF) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + # After the RPC finished, the read should also produce EOF + self.assertIs(await call.read(), aio.EOF) + + async def test_read_write_after_done_writing(self): + call = self._stub.FullDuplexCall() + + # Writes two requests, and pending two requests + await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + await call.done_writing() + + # Further write should fail + with self.assertRaises(asyncio.InvalidStateError): + await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) + + # But read should be unaffected + response = await call.read() + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + response = await call.read() + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + self.assertEqual(await call.code(), grpc.StatusCode.OK) + + async def test_error_in_async_generator(self): + # Server will pause between responses + request = messages_pb2.StreamingOutputCallRequest() + request.response_parameters.append( + messages_pb2.ResponseParameters( + size=_RESPONSE_PAYLOAD_SIZE, + interval_us=_RESPONSE_INTERVAL_US, + )) + + # We expect the request iterator to receive the exception + request_iterator_received_the_exception = asyncio.Event() + + async def request_iterator(): + with self.assertRaises(asyncio.CancelledError): + for _ in range(_NUM_STREAM_RESPONSES): + yield request + await asyncio.sleep(test_constants.SHORT_TIMEOUT) + request_iterator_received_the_exception.set() + + call = self._stub.FullDuplexCall(request_iterator()) + + # Cancel the RPC after at least one response + async def cancel_later(): + await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) + call.cancel() + + cancel_later_task = self.loop.create_task(cancel_later()) + + # No exceptions here + async for response in call: + self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) + + await request_iterator_received_the_exception.wait() + + self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) + # No failures in the cancel later task! + await cancel_later_task -# class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): - -# async def test_cancel(self): -# # Invokes the actual RPC -# call = self._stub.FullDuplexCall() - -# for _ in range(_NUM_STREAM_RESPONSES): -# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) -# response = await call.read() -# self.assertIsInstance(response, -# messages_pb2.StreamingOutputCallResponse) -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# async def test_cancel_with_pending_read(self): -# call = self._stub.FullDuplexCall() - -# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# async def test_cancel_with_ongoing_read(self): -# call = self._stub.FullDuplexCall() -# coro_started = asyncio.Event() - -# async def read_coro(): -# coro_started.set() -# await call.read() - -# read_task = self.loop.create_task(read_coro()) -# await coro_started.wait() -# self.assertFalse(read_task.done()) - -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# async def test_early_cancel(self): -# call = self._stub.FullDuplexCall() - -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# async def test_cancel_after_done_writing(self): -# call = self._stub.FullDuplexCall() -# await call.done_writing() - -# # Cancels the RPC -# self.assertFalse(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertTrue(call.cancel()) -# self.assertTrue(call.cancelled()) -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) - -# async def test_late_cancel(self): -# call = self._stub.FullDuplexCall() -# await call.done_writing() -# self.assertEqual(grpc.StatusCode.OK, await call.code()) - -# # Cancels the RPC -# self.assertTrue(call.done()) -# self.assertFalse(call.cancelled()) -# self.assertFalse(call.cancel()) -# self.assertFalse(call.cancelled()) - -# # Status is still OK -# self.assertEqual(grpc.StatusCode.OK, await call.code()) - -# async def test_async_generator(self): - -# async def request_generator(): -# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE -# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE - -# call = self._stub.FullDuplexCall(request_generator()) -# async for response in call: -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# self.assertEqual(await call.code(), grpc.StatusCode.OK) - -# async def test_too_many_reads(self): - -# async def request_generator(): -# for _ in range(_NUM_STREAM_RESPONSES): -# yield _STREAM_OUTPUT_REQUEST_ONE_RESPONSE - -# call = self._stub.FullDuplexCall(request_generator()) -# for _ in range(_NUM_STREAM_RESPONSES): -# response = await call.read() -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# self.assertIs(await call.read(), aio.EOF) - -# self.assertEqual(await call.code(), grpc.StatusCode.OK) -# # After the RPC finished, the read should also produce EOF -# self.assertIs(await call.read(), aio.EOF) - -# async def test_read_write_after_done_writing(self): -# call = self._stub.FullDuplexCall() - -# # Writes two requests, and pending two requests -# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) -# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) -# await call.done_writing() - -# # Further write should fail -# with self.assertRaises(asyncio.InvalidStateError): -# await call.write(_STREAM_OUTPUT_REQUEST_ONE_RESPONSE) - -# # But read should be unaffected -# response = await call.read() -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) -# response = await call.read() -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# self.assertEqual(await call.code(), grpc.StatusCode.OK) - -# async def test_error_in_async_generator(self): -# # Server will pause between responses -# request = messages_pb2.StreamingOutputCallRequest() -# request.response_parameters.append( -# messages_pb2.ResponseParameters( -# size=_RESPONSE_PAYLOAD_SIZE, -# interval_us=_RESPONSE_INTERVAL_US, -# )) - -# # We expect the request iterator to receive the exception -# request_iterator_received_the_exception = asyncio.Event() - -# async def request_iterator(): -# with self.assertRaises(asyncio.CancelledError): -# for _ in range(_NUM_STREAM_RESPONSES): -# yield request -# await asyncio.sleep(test_constants.SHORT_TIMEOUT) -# request_iterator_received_the_exception.set() - -# call = self._stub.FullDuplexCall(request_iterator()) - -# # Cancel the RPC after at least one response -# async def cancel_later(): -# await asyncio.sleep(test_constants.SHORT_TIMEOUT * 2) -# call.cancel() - -# cancel_later_task = self.loop.create_task(cancel_later()) - -# # No exceptions here -# async for response in call: -# self.assertEqual(_RESPONSE_PAYLOAD_SIZE, len(response.payload.body)) - -# await request_iterator_received_the_exception.wait() - -# self.assertEqual(grpc.StatusCode.CANCELLED, await call.code()) -# # No failures in the cancel later task! -# await cancel_later_task if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) From 57fa6ce33c0756569b66a1c641b9a194ef536d05 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 16:17:48 -0800 Subject: [PATCH 09/14] Make pylint happy --- src/python/grpcio_tests/tests_aio/unit/call_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/grpcio_tests/tests_aio/unit/call_test.py b/src/python/grpcio_tests/tests_aio/unit/call_test.py index e47c00a62ab..74bbc04be2f 100644 --- a/src/python/grpcio_tests/tests_aio/unit/call_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/call_test.py @@ -755,5 +755,5 @@ class TestStreamStreamCall(_MulticallableTestMixin, AioTestBase): if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig() unittest.main(verbosity=2) From ce68d53dd1069fde05129f83c34da3b39811796e Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Fri, 6 Mar 2020 16:50:11 -0800 Subject: [PATCH 10/14] Make pytype happy --- .../_cython/_cygrpc/aio/completion_queue.pyx.pxi | 1 - .../grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi | 2 -- .../grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi | 1 - .../grpcio_tests/tests_aio/unit/server_test.py | 12 +++++------- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index 4b8b326a2da..85ae0c4561a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -92,7 +92,6 @@ cdef class CallbackCompletionQueue(BaseCompletionQueue): return self._cq async def shutdown(self): - _LOGGER.debug('CallbackCompletionQueue shutdown') grpc_completion_queue_shutdown(self._cq) await self._shutdown_completed grpc_completion_queue_destroy(self._cq) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi index 1a3dd46dcf4..37ba5f0d346 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi @@ -170,7 +170,6 @@ cdef grpc_error* asyncio_resolve( char* host, char* port, grpc_resolved_addresses** res) with gil: - _LOGGER.debug('asyncio_resolve') result = native_socket.getaddrinfo(host, port) res[0] = tuples_to_resolvaddr(result) @@ -179,7 +178,6 @@ cdef void asyncio_resolve_async( grpc_custom_resolver* grpc_resolver, char* host, char* port) with gil: - _LOGGER.debug('asyncio_resolve_async') resolver = _AsyncioResolver.create(grpc_resolver) resolver.resolve(host, port) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi index 1b4f61a9c36..d8ed9a41fe7 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi @@ -754,7 +754,6 @@ cdef class AioServer: grace: An optional float indicating the length of grace period in seconds. """ - _LOGGER.debug('server shutdown') if self._status == AIO_SERVER_STATUS_READY or self._status == AIO_SERVER_STATUS_STOPPED: return diff --git a/src/python/grpcio_tests/tests_aio/unit/server_test.py b/src/python/grpcio_tests/tests_aio/unit/server_test.py index e2a2ebacf5b..8c4d20b5838 100644 --- a/src/python/grpcio_tests/tests_aio/unit/server_test.py +++ b/src/python/grpcio_tests/tests_aio/unit/server_test.py @@ -348,7 +348,7 @@ class TestServer(AioTestBase): await self._server.stop(test_constants.SHORT_TIMEOUT) - with self.assertRaises(grpc.RpcError) as exception_context: + with self.assertRaises(aio.AioRpcError) as exception_context: await call self.assertEqual(grpc.StatusCode.UNAVAILABLE, exception_context.exception.code()) @@ -383,20 +383,18 @@ class TestServer(AioTestBase): self._server.stop(test_constants.LONG_TIMEOUT), ) - with self.assertRaises(grpc.RpcError) as exception_context: + with self.assertRaises(aio.AioRpcError) as exception_context: await call self.assertEqual(grpc.StatusCode.UNAVAILABLE, exception_context.exception.code()) - @unittest.skip('https://github.com/grpc/grpc/issues/20818') async def test_shutdown_before_call(self): - server_target, server, _ = _start_test_server() - await server.stop(None) + await self._server.stop(None) # Ensures the server is cleaned up at this point. # Some proper exception should be raised. - async with aio.insecure_channel('localhost:%d' % port) as channel: - await channel.unary_unary(_SIMPLE_UNARY_UNARY)(_REQUEST) + with self.assertRaises(aio.AioRpcError): + await self._channel.unary_unary(_SIMPLE_UNARY_UNARY)(_REQUEST) async def test_unimplemented(self): call = self._channel.unary_unary(_UNIMPLEMENTED_METHOD) From 231f9c0c94ab5361583d86e8bafb44e3a5513236 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Mon, 9 Mar 2020 14:14:57 -0700 Subject: [PATCH 11/14] Improve readability --- .../_cygrpc/aio/completion_queue.pxd.pxi | 2 +- .../_cygrpc/aio/completion_queue.pyx.pxi | 12 +-- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 100 +++++++++--------- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi index 708a2745fdf..2b3be97cb3b 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi @@ -23,7 +23,7 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): cdef object _poller cdef object _poller_running - cdef _polling(self) + cdef void _poll(self) except * cdef class CallbackCompletionQueue(BaseCompletionQueue): diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index 85ae0c4561a..c09c276a67a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -36,11 +36,11 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): self._shutdown_completed = asyncio.get_event_loop().create_future() self._poller = None self._poller_running = asyncio.get_event_loop().create_future() - self._poller = threading.Thread(target=self._polling_wrapper) + self._poller = threading.Thread(target=self._poll_wrapper) self._poller.daemon = True self._poller.start() - cdef _polling(self): + cdef void _poll(self) except *: cdef grpc_event event cdef CallbackContext *context cdef object waiter @@ -53,7 +53,7 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): NULL) if event.type == GRPC_QUEUE_TIMEOUT: - raise NotImplementedError() + raise AssertionError("Core should not return timeout error!") elif event.type == GRPC_QUEUE_SHUTDOWN: self._shutdown = True grpc_call_soon_threadsafe(self._shutdown_completed.set_result, None) @@ -64,8 +64,8 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): context.callback_wrapper, event.success) - def _polling_wrapper(self): - self._polling() + def _poll_wrapper(self): + self._poll() async def shutdown(self): grpc_completion_queue_shutdown(self._cq) @@ -103,4 +103,4 @@ cdef BaseCompletionQueue create_completion_queue(): elif grpc_aio_engine is AsyncIOEngine.POLLER: return PollerCompletionQueue() else: - raise ValueError('Unexpected engine type [%s]' % grpc_aio_engine) + raise ValueError('Unsupported engine type [%s]' % grpc_aio_engine) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index 27a37dfe053..bf76dfebd6a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -15,13 +15,14 @@ cdef bint _grpc_aio_initialized = False # NOTE(lidiz) Theoretically, applications can run in multiple event loops as -# long as they are in the same thread with same magic. However, I don't think -# we should support this use case. So, the gRPC Python Async Stack should use -# a single event loop picked by "init_grpc_aio". -cdef object _grpc_aio_loop -cdef object _event_loop_thread_ident +# long as they are in the same thread with same magic. This is not a supported +# use case. So, the gRPC Python Async Stack should use a single event loop +# picked by "init_grpc_aio". +cdef object _grpc_aio_loop # asyncio.AbstractEventLoop +cdef int64_t _event_loop_thread_ident cdef str _GRPC_ASYNCIO_ENGINE = os.environ.get('GRPC_ASYNCIO_ENGINE', 'default').lower() grpc_aio_engine = None +cdef object _grpc_initialization_lock = threading.Lock() class AsyncIOEngine(enum.Enum): @@ -36,51 +37,50 @@ def init_grpc_aio(): global _event_loop_thread_ident global grpc_aio_engine - # Marks this function as called - if _grpc_aio_initialized: - return - else: - _grpc_aio_initialized = True - - # Picks the engine for gRPC AsyncIO Stack - for engine_type in AsyncIOEngine: - if engine_type.value == _GRPC_ASYNCIO_ENGINE: - grpc_aio_engine = engine_type - break - if grpc_aio_engine is None or grpc_aio_engine is AsyncIOEngine.DEFAULT: - grpc_aio_engine = AsyncIOEngine.CUSTOM_IO_MANAGER - - # Anchors the event loop that the gRPC library going to use. - _grpc_aio_loop = asyncio.get_event_loop() - _event_loop_thread_ident = threading.current_thread().ident - - if grpc_aio_engine is AsyncIOEngine.CUSTOM_IO_MANAGER: - # Activates asyncio IO manager. - # NOTE(lidiz) Custom IO manager must be activated before the first - # `grpc_init()`. Otherwise, some special configurations in Core won't - # pick up the change, and resulted in SEGFAULT or ABORT. - install_asyncio_iomgr() - - # TODO(https://github.com/grpc/grpc/issues/22244) we need a the - # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC - # library won't shutdown cleanly. - grpc_init() - - # Timers are triggered by the Asyncio loop. We disable - # the background thread that is being used by the native - # gRPC iomgr. - grpc_timer_manager_set_threading(False) - - # gRPC callbaks are executed within the same thread used by the Asyncio - # event loop, as it is being done by the other Asyncio callbacks. - Executor.SetThreadingAll(False) - else: - # TODO(https://github.com/grpc/grpc/issues/22244) we need a the - # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC - # library won't shutdown cleanly. - grpc_init() - - _grpc_aio_initialized = False + with _grpc_initialization_lock: + # Marks this function as called + if _grpc_aio_initialized: + return + else: + _grpc_aio_initialized = True + + # Picks the engine for gRPC AsyncIO Stack + for engine_type in AsyncIOEngine: + if engine_type.value == _GRPC_ASYNCIO_ENGINE: + grpc_aio_engine = engine_type + break + if grpc_aio_engine is None or grpc_aio_engine is AsyncIOEngine.DEFAULT: + grpc_aio_engine = AsyncIOEngine.CUSTOM_IO_MANAGER + + # Anchors the event loop that the gRPC library going to use. + _grpc_aio_loop = asyncio.get_event_loop() + _event_loop_thread_ident = threading.current_thread().ident + + if grpc_aio_engine is AsyncIOEngine.CUSTOM_IO_MANAGER: + # Activates asyncio IO manager. + # NOTE(lidiz) Custom IO manager must be activated before the first + # `grpc_init()`. Otherwise, some special configurations in Core won't + # pick up the change, and resulted in SEGFAULT or ABORT. + install_asyncio_iomgr() + + # TODO(https://github.com/grpc/grpc/issues/22244) we need a the + # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC + # library won't shutdown cleanly. + grpc_init() + + # Timers are triggered by the Asyncio loop. We disable + # the background thread that is being used by the native + # gRPC iomgr. + grpc_timer_manager_set_threading(False) + + # gRPC callbaks are executed within the same thread used by the Asyncio + # event loop, as it is being done by the other Asyncio callbacks. + Executor.SetThreadingAll(False) + else: + # TODO(https://github.com/grpc/grpc/issues/22244) we need a the + # grpc_shutdown_blocking() counterpart for this call. Otherwise, the gRPC + # library won't shutdown cleanly. + grpc_init() def grpc_aio_loop(): From 5246a7c8ffb2ec888bc9ad4a7839d6a48e6bbd27 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Mon, 9 Mar 2020 15:18:03 -0700 Subject: [PATCH 12/14] Link to a TODO issue --- src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi index 37ba5f0d346..ac62c41e0f2 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/iomgr/iomgr.pyx.pxi @@ -188,6 +188,7 @@ cdef void asyncio_timer_start(grpc_custom_timer* grpc_timer) with gil: cdef void asyncio_timer_stop(grpc_custom_timer* grpc_timer) with gil: + # TODO(https://github.com/grpc/grpc/issues/22278) remove this if condition if grpc_timer.timer == NULL: return else: From 5b1ecef47fe2b646746c0e9c26a947b89cedb6c3 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Mon, 9 Mar 2020 15:38:34 -0700 Subject: [PATCH 13/14] Simplify the Cython code --- .../_cygrpc/aio/completion_queue.pxd.pxi | 6 ++---- .../_cygrpc/aio/completion_queue.pyx.pxi | 21 ++++++------------- .../grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi | 4 ++-- .../grpcio/grpc/experimental/aio/_server.py | 2 +- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi index 2b3be97cb3b..54a0e90184a 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pxd.pxi @@ -13,20 +13,18 @@ # limitations under the License. cdef class BaseCompletionQueue: + cdef grpc_completion_queue *_cq cdef grpc_completion_queue* c_ptr(self) cdef class PollerCompletionQueue(BaseCompletionQueue): - cdef grpc_completion_queue *_cq cdef bint _shutdown cdef object _shutdown_completed - cdef object _poller - cdef object _poller_running + cdef object _poller_thread cdef void _poll(self) except * cdef class CallbackCompletionQueue(BaseCompletionQueue): - cdef grpc_completion_queue *_cq cdef object _shutdown_completed # asyncio.Future cdef CallbackWrapper _wrapper diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index c09c276a67a..33d1f348c75 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -25,7 +25,7 @@ cdef class BaseCompletionQueue: raise NotImplementedError() cdef grpc_completion_queue* c_ptr(self): - raise NotImplementedError() + return self._cq cdef class PollerCompletionQueue(BaseCompletionQueue): @@ -34,17 +34,13 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): self._cq = grpc_completion_queue_create_for_next(NULL) self._shutdown = False self._shutdown_completed = asyncio.get_event_loop().create_future() - self._poller = None - self._poller_running = asyncio.get_event_loop().create_future() - self._poller = threading.Thread(target=self._poll_wrapper) - self._poller.daemon = True - self._poller.start() + self._poller_thread = threading.Thread(target=self._poll_wrapper, daemon=True) + self._poller_thread.start() cdef void _poll(self) except *: cdef grpc_event event cdef CallbackContext *context cdef object waiter - grpc_call_soon_threadsafe(self._poller_running.set_result, None) while not self._shutdown: with nogil: @@ -56,10 +52,10 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): raise AssertionError("Core should not return timeout error!") elif event.type == GRPC_QUEUE_SHUTDOWN: self._shutdown = True - grpc_call_soon_threadsafe(self._shutdown_completed.set_result, None) + aio_loop_call_soon_threadsafe(self._shutdown_completed.set_result, None) else: context = event.tag - grpc_call_soon_threadsafe( + aio_loop_call_soon_threadsafe( _handle_callback_wrapper, context.callback_wrapper, event.success) @@ -71,9 +67,7 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): grpc_completion_queue_shutdown(self._cq) await self._shutdown_completed grpc_completion_queue_destroy(self._cq) - - cdef grpc_completion_queue* c_ptr(self): - return self._cq + self._poller_thread.join() cdef class CallbackCompletionQueue(BaseCompletionQueue): @@ -88,9 +82,6 @@ cdef class CallbackCompletionQueue(BaseCompletionQueue): 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 diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi index bf76dfebd6a..9a6fdecace3 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/grpc_aio.pyx.pxi @@ -88,7 +88,7 @@ def grpc_aio_loop(): return _grpc_aio_loop -def grpc_schedule_coroutine(object coro): +def aio_loop_schedule_coroutine(object coro): """Thread-safely schedules coroutine to gRPC Aio event loop. If invoked within the same thread as the event loop, return an @@ -102,7 +102,7 @@ def grpc_schedule_coroutine(object coro): return _grpc_aio_loop.create_task(coro) -def grpc_call_soon_threadsafe(object func, *args): +def aio_loop_call_soon_threadsafe(object func, *args): # TODO(lidiz) After we are confident, we can drop this assert. Otherwsie, # we should limit this function to non-grpc-event-loop thread. assert _event_loop_thread_ident != threading.current_thread().ident diff --git a/src/python/grpcio/grpc/experimental/aio/_server.py b/src/python/grpcio/grpc/experimental/aio/_server.py index ec0af1d51f3..dc90c0a6dca 100644 --- a/src/python/grpcio/grpc/experimental/aio/_server.py +++ b/src/python/grpcio/grpc/experimental/aio/_server.py @@ -162,7 +162,7 @@ class Server(_base_server.Server): be safe to slightly extend the underlying Cython object's life span. """ if hasattr(self, '_server'): - cygrpc.grpc_schedule_coroutine(self._server.shutdown(None)) + cygrpc.aio_loop_schedule_coroutine(self._server.shutdown(None)) def server(migration_thread_pool: Optional[Executor] = None, From 4741da3a111250f42bb69d83f58db7a5599b2a0b Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Tue, 10 Mar 2020 10:26:46 -0700 Subject: [PATCH 14/14] Remove unused variable --- .../grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi index 33d1f348c75..7dd1f68f10c 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi @@ -40,7 +40,6 @@ cdef class PollerCompletionQueue(BaseCompletionQueue): cdef void _poll(self) except *: cdef grpc_event event cdef CallbackContext *context - cdef object waiter while not self._shutdown: with nogil: