def __del__(self): self._pypy_issue2786_workaround.discard(self._coroutine) if getcoroutinestate(self._coroutine) is CORO_CREATED: # Never started, nothing to clean up, just suppress the "coroutine # never awaited" message. self._coroutine.close() if getcoroutinestate( self._coroutine) is CORO_SUSPENDED and not self._closed: if self._finalizer is not None: self._finalizer(self) else: # Mimic the behavior of native generators on GC with no finalizer: # throw in GeneratorExit, run for one turn, and complain if it didn't # finish. thrower = self.athrow(GeneratorExit) try: thrower.send(None) except (GeneratorExit, StopAsyncIteration): pass except StopIteration: raise RuntimeError("async_generator ignored GeneratorExit") else: raise RuntimeError( "async_generator {!r} awaited during finalization; install " "a finalization hook to support this, or wrap it in " "'async with aclosing(...):'".format( self.ag_code.co_name)) finally: thrower.close()
async def test_normal_exit2(nursery, ed): '''nursery.start_soon() instead of nursery.start()''' from inspect import ( getcoroutinestate, CORO_CREATED, CORO_SUSPENDED, CORO_CLOSED, ) import trio import asynckivy as ak from asynckivy.compatibility.trio import run_coro_under_trio async def ak_func(): await ak.event(ed, 'on_test') ak_coro = ak_func() async def trio_func(): await run_coro_under_trio(ak_coro) nonlocal done; done = True done = False nursery.start_soon(trio_func) assert getcoroutinestate(ak_coro) == CORO_CREATED assert not done await trio.sleep(.01) assert getcoroutinestate(ak_coro) == CORO_SUSPENDED assert not done ed.dispatch('on_test') assert getcoroutinestate(ak_coro) == CORO_CLOSED await trio.sleep(.01) assert done
def test_cr_await(self): @types.coroutine def a(): self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING) self.assertIsNone(coro_b.cr_await) yield self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING) self.assertIsNone(coro_b.cr_await) async def c(): await a() async def b(): self.assertIsNone(coro_b.cr_await) await c() self.assertIsNone(coro_b.cr_await) coro_b = b() self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CREATED) self.assertIsNone(coro_b.cr_await) coro_b.send(None) self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_SUSPENDED) self.assertEqual(coro_b.cr_await.cr_await.gi_code.co_name, 'a') with self.assertRaises(StopIteration): coro_b.send(None) # complete coroutine self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CLOSED) self.assertIsNone(coro_b.cr_await)
def a(): self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING) self.assertIsNone(coro_b.cr_await) yield self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING) self.assertIsNone(coro_b.cr_await)
def __del__(self): if getcoroutinestate(self._coroutine) is CORO_CREATED: # Never started, nothing to clean up, just suppress the "coroutine # never awaited" message. self._coroutine.close() if getcoroutinestate(self._coroutine) is CORO_SUSPENDED: # This exception will get swallowed because this is __del__, but # it's an easy way to trigger the print-to-console logic raise RuntimeError( "partially-exhausted async_generator garbage collected")
def format_execution_point(coro): if asyncio.iscoroutine(coro) or inspect.isasyncgen(coro): if inspect.iscoroutine(coro): t = 'coroutine' s = inspect.getcoroutinestate(coro) c = coro.cr_code f = coro.cr_frame elif inspect.isgenerator(coro): t = 'generator' s = inspect.getgeneratorstate(coro) c = coro.gi_code f = coro.gi_frame elif inspect.isasyncgen(coro): t = 'async_generator' s = getasyncgenstate(coro) f = coro.ag_frame c = coro.ag_code else: return f"(unsupported coroutine type {type(coro)!r})" ref = f'{c.co_filename}:{c.co_firstlineno}:{c.co_name}' if s.endswith('CLOSED'): return f'{t} {ref} just finished' elif s.endswith('SUSPENDED'): return f'{t} {ref} stopped at line {f.f_lineno}' else: assert False, f'Unexpected state {s} for {coro!r})' else: return f"(can't get execution point for {coro!r})"
def body(state): if inspect.getcoroutinestate(x) == inspect.CORO_CLOSED: _x = call() else: _x = x next_ = lambda: _x.send(None) while True: try: future = next_() except StopIteration as e: val = e.value break else: assert isinstance(future, cls) try: val, state = future._f(state) except Exception as e: # We need an intermediate variable here, since # "e" is not really bound in this scope. excep = e next_ = lambda: _x.throw(excep) else: next_ = lambda: _x.send(val) if isinstance(val, cls): return val._f(state) else: return (val, state)
def _do_it(self, start_fn, *args): if not self._hooks_inited: self._hooks_inited = True (firstiter, self._finalizer) = get_asyncgen_hooks() if firstiter is not None: firstiter(self) if sys.implementation.name == "pypy": self._pypy_issue2786_workaround.add(self._coroutine) # On CPython 3.5.2 (but not 3.5.0), coroutines get cranky if you try # to iterate them after they're exhausted. Generators OTOH just raise # StopIteration. We want to convert the one into the other, so we need # to avoid iterating stopped coroutines. if getcoroutinestate(self._coroutine) is CORO_CLOSED: raise StopAsyncIteration() async def step(): if self.ag_running: raise ValueError("async generator already executing") try: self.ag_running = True return await ANextIter(self._it, start_fn, *args) except StopAsyncIteration: self._pypy_issue2786_workaround.discard(self._coroutine) raise finally: self.ag_running = False return step()
async def _await_later(self, delay: t.Union[int, float], task_id: t.Hashable, coroutine: t.Coroutine) -> None: """Await `coroutine` after the given `delay` number of seconds.""" try: self._log.info( f"Waiting {delay} seconds before awaiting coroutine for #{task_id}." ) await asyncio.sleep(delay) # Use asyncio.shield to prevent the coroutine from cancelling itself. self._log.info( f"Done waiting for #{task_id}; now awaiting the coroutine.") await asyncio.shield(coroutine) finally: # Close it to prevent unawaited coroutine warnings, # which would happen if the task was cancelled during the sleep. # Only close it if it's not been awaited yet. This check is important because the # coroutine may cancel this task, which would also trigger the finally block. state = inspect.getcoroutinestate(coroutine) if state == "CORO_CREATED": self._log.debug( f"Explicitly closing the coroutine for #{task_id}.") coroutine.close() else: self._log.debug( f"Finally block reached for #{task_id}; {state=}")
async def atomize_cancellation( coro: Coroutine[_T], *, loop: typing.Optional[asyncio.AbstractEventLoop] = None) -> _T: if loop is None: loop = asyncio.get_event_loop() task = loop.create_task(coro) was_cancelled = False while True: try: result = await shield(task, loop=loop) except asyncio.CancelledError: if task.cancelled(): raise if inspect.getcoroutinestate(coro) == inspect.CORO_CREATED: task.cancel() raise was_cancelled = True else: break if was_cancelled: asyncio.Task.current_task(loop).cancel() return result
def monitor(self): """Make a curses window to show various jobs managed by the AsyncLoop. Note1: UNIX ONLY, since this method simply imports `curses` module, which is not available for Windows. Note2: This is an untested method, and I am not sure how to test this. """ import curses import inspect stdscr = curses.initscr() curses.curs_set(0) curses.noecho() curses.cbreak() width_split = curses.COLS // 3 - 1 win_done = curses.newwin(curses.LINES - 1, width_split, 0, 0) win_running = curses.newwin(curses.LINES - 1, width_split, 0, width_split + 1) win_pending = curses.newwin(curses.LINES - 1, width_split, 0, 2 * width_split + 1) stdscr.addstr(curses.LINES - 1, 0, 'Monitoring started. Press Ctrl+C to stop.') stdscr.refresh() win_done.addstr(0, 0, 'DONE') win_pending.addstr(0, 0, 'PENDING') while True: try: win_done.addstr(1, 0, f'{len(self.done)} jobs done') list_done = list(self.done)[:curses.LINES - 3] for idx, fut in enumerate(list_done, start=2): fmt_str = f'{id(fut):x} {fut._state}' win_done.addstr(idx, 0, fmt_str) win_done.refresh() win_running.clear() win_running.addstr(0, 0, 'RUNNING') win_running.addstr(1, 0, f'{self.running.qsize()} jobs running') list_running = list(self.running.items())[:curses.LINES - 3] for idx, (fut, coro) in enumerate(list_running, start=2): coro_state = inspect.getcoroutinestate(coro) fmt_str = f'{id(fut):x} {coro_state}' win_running.addstr(idx, 0, fmt_str) win_running.refresh() win_pending.clrtoeol() win_pending.addstr(1, 0, f'{self.pending.qsize()} jobs pending') win_pending.refresh() time.sleep(.1) except KeyboardInterrupt: break curses.nocbreak() curses.echo() curses.endwin()
def test_cancel_without_start(): from inspect import getcoroutinestate, CORO_CLOSED import asyncgui as ag task = ag.Task(ag.sleep_forever()) task.cancel() assert task.cancelled assert task._exception is None assert getcoroutinestate(task.root_coro) == CORO_CLOSED
def validate_coro(self, coro: Awaitable) -> None: """Validate that a provided coroutine is actually awaitable""" if not inspect.isawaitable(coro): raise ConcurrentError( f"Provided input was not a coroutine: {coro}") if inspect.getcoroutinestate(coro) != inspect.CORO_CREATED: raise ConcurrentError( f"Provided coroutine has already been fired: {coro}")
def whatis(self, arguments): """Prints the type of the argument. Usage: whatis <name>... """ arg = " ".join(arguments["argv"][1:]) try: value = eval(arg, self._obj.curframe.f_globals, self._obj.curframe.f_locals) except: # noqa v = sys.exc_info()[1] self._ui.printf('*** %R{}%N: {}\n'.format(type(v).__name__, v)) return if inspect.ismodule(value): self._ui.print(str(value)) elif inspect.isasyncgenfunction(value): self._ui.print('Async Gen function:', value.__name__, inspect.signature(value)) elif inspect.isasyncgen(value): self._ui.print('Async Gen:', value.__name__, inspect.signature(value)) elif inspect.iscoroutine(value): self._ui.print('Coroutine:', value) self._ui.print(' state:', inspect.getcoroutinestate(value)) if inspect.isawaitable(value): self._ui.print(' and awaitable.') self._ui.print(' stack:', _coroutine_format_stack(value, complete=False)) elif inspect.isgenerator(value): self._ui.print('Generator:', value) self._ui.print(' state:', inspect.getgeneratorstate(value)) if inspect.isawaitable(value): self._ui.print(' and awaitable.') elif inspect.iscoroutinefunction(value): self._ui.print('Coroutine function:', value.__name__, inspect.signature(value)) elif inspect.isgeneratorfunction(value): self._ui.print('Generator function:', value.__name__, inspect.signature(value)) elif inspect.isfunction(value): self._ui.print('Function:', value.__name__, inspect.signature(value)) elif inspect.ismethod(value): self._ui.print('Method:', value.__name__, inspect.signature(value)) elif inspect.iscode(value): self._ui.print('Code object:', value.co_name) elif inspect.isclass(value): self._ui.print('Class:', value.__name__) elif inspect.ismethoddescriptor(value): self._ui.print('Method descriptor:', value.__name__) elif inspect.isdatadescriptor(value): self._ui.print('Data descriptor:', value.__name__) # None of the above... else: self._ui.print(repr(type(value)))
def step_coro(*args, **kwargs): try: if getcoroutinestate(coro) != CORO_CLOSED: coro.send(( args, kwargs, ))(step_coro) except StopIteration: pass
def _step_coro(self, *args, **kwargs): coro = self._root_coro try: if getcoroutinestate(coro) != CORO_CLOSED: coro.send((args, kwargs, ))(self._step_coro) except StopIteration: pass else: if self._cancel_called and self._is_cancellable: self._actual_cancel()
async def test_spawn_after_shutdown(self): async def activity(value): return value async with Scope() as scope: pass payload = activity(3) with pytest.raises(RuntimeError): scope.do(payload) assert inspect.getcoroutinestate(payload) == inspect.CORO_CLOSED
async def test_normal_exit(nursery, ed): from inspect import getcoroutinestate, CORO_SUSPENDED, CORO_CLOSED import trio import asynckivy as ak from asynckivy.compatibility.trio import run_coro_under_trio async def ak_func(): await ak.event(ed, 'on_test') ak_coro = ak_func() async def trio_func(*, task_status=trio.TASK_STATUS_IGNORED): await run_coro_under_trio(ak_coro, task_status=task_status) nonlocal done; done = True done = False await nursery.start(trio_func) assert getcoroutinestate(ak_coro) == CORO_SUSPENDED assert not done ed.dispatch('on_test') assert getcoroutinestate(ak_coro) == CORO_CLOSED await trio.sleep(.01) assert done
async def _do_it(self, start_fn, *args): # On CPython 3.5.2 (but not 3.5.0), coroutines get cranky if you try # to iterate them after they're exhausted. Generators OTOH just raise # StopIteration. We want to convert the one into the other, so we need # to avoid iterating stopped coroutines. if getcoroutinestate(self._coroutine) is CORO_CLOSED: raise StopAsyncIteration() if self.ag_running: raise ValueError("async generator already executing") try: self.ag_running = True return await ANextIter(self._it, start_fn, *args) finally: self.ag_running = False
def __repr__(self): cstate = '' if inspect.iscoroutine(self.coro): crst = inspect.getcoroutinestate(self.coro) cstate = f' {crst} {self._coro_info(self.coro)}' tstate = self.state if self.is_done(): result = self.result(False) if result is not None: tstate += f'={result!r}' return (f'<{self.__class__.__name__}' f' {tstate}' f' #{self.task_id}@{self.creation}{cstate}' '>')
async def aclose(self): state = getcoroutinestate(self._coroutine) if state is CORO_CREATED: # Make sure that aclose() on an unstarted generator returns # successfully and prevents future iteration. self._it.close() return elif state is CORO_CLOSED: return try: await self.athrow(GeneratorExit) except (GeneratorExit, StopAsyncIteration): pass else: raise RuntimeError("async_generator ignored GeneratorExit")
async def _delayed_coro(self, delay: t.Union[float, int], coro: t.Awaitable, task_id): try: #await the timed delay log.debug(f'Waiting {delay} seconds before executing coroutine with Id: {task_id}') await asyncio.sleep(delay) #time is up, execute the callback log.debug(f'Delay complete for coroutine {task_id}; executing coroutine') await asyncio.shield(coro) #use a finally so that the coro is closed even if it throws finally: if inspect.getcoroutinestate(coro) == 'CORO_CREATED': log.debug(f'Explicitly closing the coroutine for #{task_id}.') coro.close() else: log.debug(f'Finally block reached for #{task_id}')
def _coro_info(klass, coro): if not inspect.iscoroutine(coro): return f'{coro!r}' result = [] result.append(coro.__name__) c = coro while True: if inspect.getcoroutinestate(c) == 'CORO_SUSPENDED': if inspect.isgenerator(c.cr_await): result.append( f'{c.cr_await.__name__}@{klass._frame(c.cr_frame)}') else: c = c.cr_await continue break return ' '.join(x for x in result if x)
async def _postpone(self, task_name: t.Hashable, coro: t.Coroutine, delay: t.Union[int, float]) -> None: """ Execute `coro` after `delay` amount of seconds. In case the `coro` would get cancelled during the sleep, close it to prevent never awaited error. """ try: await asyncio.sleep(delay) # Prevent `coro` from canceling itself by shielding it await asyncio.shield(coro) finally: # Prevent coroutine never awaited error if it got cancelled during sleep # But only do so if it wasn't awaited yet, since the coro can also cancel itself if inspect.getcoroutinestate(coro) == "CORO_CREATED": logger.debug( f"Aborting the coroutine from {self.id}:{task_name} task.") coro.close()
async def aclose(self): state = getcoroutinestate(self._coroutine) if state is CORO_CLOSED or self._closed: return # Make sure that even if we raise "async_generator ignored # GeneratorExit", and thus fail to exhaust the coroutine, # __del__ doesn't complain again. self._closed = True if state is CORO_CREATED: # Make sure that aclose() on an unstarted generator returns # successfully and prevents future iteration. self._it.close() return try: await self.athrow(GeneratorExit) except (GeneratorExit, StopAsyncIteration): self._pypy_issue2786_workaround.discard(self._coroutine) else: raise RuntimeError("async_generator ignored GeneratorExit")
async def child_a(ctx): from inspect import getcoroutinestate, CORO_RUNNING import asyncgui as ag await ctx['e_begin'].wait() await ctx['e'].wait() assert getcoroutinestate(ctx['task_b'].root_coro) == CORO_RUNNING ctx['main_task'].cancel() what = ctx['what_a_should_do'] if what == 'nothing': return elif what == 'suspend': await ag.sleep_forever() elif what == 'fail': raise ZeroDivisionError elif what == 'cancel_self': task_a = await ag.get_current_task() task_a.cancel() await ag.sleep_forever() else: pytest.fail(f"Invalid value: {what}")
def schedule(self, task_id: t.Hashable, coroutine: t.Coroutine) -> None: """ Schedule the execution of a `coroutine`. If a task with `task_id` already exists, close `coroutine` instead of scheduling it. This prevents unawaited coroutine warnings. Don't pass a coroutine that'll be re-used elsewhere. """ self._log.trace(f"Scheduling task #{task_id}...") msg = f"Cannot schedule an already started coroutine for #{task_id}" assert inspect.getcoroutinestate(coroutine) == "CORO_CREATED", msg if task_id in self._scheduled_tasks: self._log.debug(f"Did not schedule task #{task_id}; task was already scheduled.") coroutine.close() return task = asyncio.create_task(coroutine, name=f"{self.name}_{task_id}") task.add_done_callback(partial(self._task_done_callback, task_id)) self._scheduled_tasks[task_id] = task self._log.debug(f"Scheduled task #{task_id} {id(task)}.")
def start(coro_or_task: Coro_or_Task) -> Coro_or_Task: '''Starts a asynckivy-flavored coroutine or a Task. It is recommended to pass a Task instead of a coroutine if a coroutine is going to live long time, as Task is flexibler and has information that may be useful for debugging. Returns the argument itself. ''' def step_coro(*args, **kwargs): try: if getcoroutinestate(coro) != CORO_CLOSED: coro.send(( args, kwargs, ))(step_coro) except StopIteration: pass if isinstance(coro_or_task, Task): task = coro_or_task if task._state is not TaskState.CREATED: raise ValueError(f"{task} was already started") step_coro._task = task coro = task.root_coro else: coro = coro_or_task if getcoroutinestate(coro) != CORO_CREATED: raise ValueError("Coroutine was already started") step_coro._task = None try: coro.send(None)(step_coro) except StopIteration: pass return coro_or_task
def safe_send(gen, data): state = inspect.getcoroutinestate(gen) if state not in {"CORO_RUNNING", "CORO_CLOSED"}: return send(gen, data)
def _is_cancellable(self) -> bool: '''Whether the task can immediately be cancelled.''' return (not self._cancel_protection) and \ getcoroutinestate(self._root_coro) != CORO_RUNNING
def main(): ########################################################################## # native coroutine example ########################################################################## print(str.format( 'coroutine_function function is coroutine function => {}', inspect.iscoroutinefunction(coroutine_function) )) print(str.format( 'coroutine_function type => {}', type(coroutine_function) )) # create native python coroutine coroutine = coroutine_function() print(str.format( 'coroutine is coroutine => {}', inspect.iscoroutine(coroutine) )) print(str.format('coroutine type => {}', type(coroutine))) print(str.format( 'coroutine state => {}', inspect.getcoroutinestate(coroutine) )) print(str.format( 'coroutine locals => {}', inspect.getcoroutinelocals(coroutine) )) ########################################################################## # generator example ########################################################################## print(str.format( 'generator_function function is generator function => {}', inspect.isgeneratorfunction(generator_function) )) print(str.format( 'generator_function type => {}', type(generator_function) )) # create generator object generator = generator_function() print(str.format( 'generator is generator object => {}', inspect.isgenerator(generator) )) print(str.format('generator object type => {}', type(generator))) print(str.format( 'generator object state => {}', inspect.getgeneratorstate(generator) )) # iterate generator generator_result1 = next(generator) print(str.format( 'generator object last iterate result => {}', generator_result1 )) print(str.format( 'generator object state => {}', inspect.getgeneratorstate(generator) )) try: generator_result2 = next(generator) print(str.format( 'generator object last iterate result => {}', generator_result2 )) except StopIteration: print(str.format('[#] generator object iterated')) print(str.format( 'generator object state => {}', inspect.getgeneratorstate(generator) )) ########################################################################## # ordinary function example ########################################################################## print(str.format( 'ordinary_function() is ordinary function: {}', inspect.isfunction(ordinary_function) ))