async def _close(self, grace): if self._channel.closed(): return # No new calls will be accepted by the Cython channel. self._channel.closing() # Iterate through running tasks tasks = _all_tasks() calls = [] call_tasks = [] for task in tasks: stack = task.get_stack(limit=1) # If the Task is created by a C-extension, the stack will be empty. if not stack: continue # Locate ones created by `aio.Call`. frame = stack[0] candidate = frame.f_locals.get('self') if candidate: if isinstance(candidate, _base_call.Call): if hasattr(candidate, '_channel'): # For intercepted Call object if candidate._channel is not self._channel: continue elif hasattr(candidate, '_cython_call'): # For normal Call object if candidate._cython_call._channel is not self._channel: continue else: # Unidentified Call object raise cygrpc.InternalError( f'Unrecognized call object: {candidate}') calls.append(candidate) call_tasks.append(task) # If needed, try to wait for them to finish. # Call objects are not always awaitables. if grace and call_tasks: await asyncio.wait(call_tasks, timeout=grace, loop=self._loop) # Time to cancel existing calls. for call in calls: call.cancel() # Destroy the channel self._channel.close()
async def _close(self, grace): # pylint: disable=too-many-branches if self._channel.closed(): return # No new calls will be accepted by the Cython channel. self._channel.closing() # Iterate through running tasks tasks = _all_tasks() calls = [] call_tasks = [] for task in tasks: try: stack = task.get_stack(limit=1) except AttributeError as attribute_error: # NOTE(lidiz) tl;dr: If the Task is created with a CPython # object, it will trigger AttributeError. # # In the global finalizer, the event loop schedules # a CPython PyAsyncGenAThrow object. # https://github.com/python/cpython/blob/00e45877e33d32bb61aa13a2033e3bba370bda4d/Lib/asyncio/base_events.py#L484 # # However, the PyAsyncGenAThrow object is written in C and # failed to include the normal Python frame objects. Hence, # this exception is a false negative, and it is safe to ignore # the failure. It is fixed by https://github.com/python/cpython/pull/18669, # but not available until 3.9 or 3.8.3. So, we have to keep it # for a while. # TODO(lidiz) drop this hack after 3.8 deprecation if 'frame' in str(attribute_error): continue else: raise # If the Task is created by a C-extension, the stack will be empty. if not stack: continue # Locate ones created by `aio.Call`. frame = stack[0] candidate = frame.f_locals.get('self') if candidate: if isinstance(candidate, _base_call.Call): if hasattr(candidate, '_channel'): # For intercepted Call object if candidate._channel is not self._channel: continue elif hasattr(candidate, '_cython_call'): # For normal Call object if candidate._cython_call._channel is not self._channel: continue else: # Unidentified Call object raise cygrpc.InternalError( f'Unrecognized call object: {candidate}') calls.append(candidate) call_tasks.append(task) # If needed, try to wait for them to finish. # Call objects are not always awaitables. if grace and call_tasks: await asyncio.wait(call_tasks, timeout=grace, loop=self._loop) # Time to cancel existing calls. for call in calls: call.cancel() # Destroy the channel self._channel.close()