Giter Club home page Giter Club logo

awaitwhat's Introduction

Await, What?

Tells you what waits for what in an async/await program.

Python 3.10.0a1

It seems the API was changed in 3.10 and the C extension doesn't compile. I'll investigate...

Alpine

You'll need apk add build-base openssl-dev libffi-dev

2019 Sprint Setup

Comms: https://gitter.im/awaitwhat/community

  • Python 3.9, Python 3.8 (preferred) or Python 3.7
  • Your platform dev tools (compiler, etc).
  • Ensure that python is 3.9 or 3.8 or 3.7
  • Install poetry
  • Install graphviz
  • Clone this repository
  • Look at tests
  • Look at issues
> python --version
Python 3.9.0b4  #๐Ÿงก
Python 3.8.4    #๐Ÿ‘Œ

> dot -V
dot - graphviz version 2.40.1

> curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
# add ~/.poetry/bin to your PATH

> git clone [email protected]:dimaqq/awaitwhat.git
> cd awaitwhat
~/awaitwhat (dev|โœ”) > poetry shell    # creates a venv and drops you in it

(awaitwhat-x-py3.9) ~/awaitwhat (dev|โœ”) > poetry install  # installs projects dependencies in a venv
(awaitwhat-x-py3.9) ~/awaitwhat (dev|โœ”) > poetry build    # builds a C extension in this project

(awaitwhat-x-py3.9) ~/awaitwhat (dev|โœ”) > env PYTHONPATH=. python examples/test_shield.py | tee graph.dot
(awaitwhat-x-py3.9) ~/awaitwhat (dev|โœ”) > dot -Tsvg graph.dot -o graph.svg
(awaitwhat-x-py3.9) ~/awaitwhat (dev|โœ”) > open graph.svg  # or load it in a browser

TL;DR

Say you have this code:

async def job():
    await foo()


async def foo():
    await bar()


async def bar():
    await baz()


async def baz():
    await leaf()


async def leaf():
    await asyncio.sleep(1)  # imagine you don't know this


async def work():
    await asyncio.gather(..., job())

Now that code is stuck and and you want to know why.

Python built-in

Stack for <Task pending coro=<job() โ€ฆ> wait_for=<Future pending cb=[<TaskWakeupMethWrapper โ€ฆ>()]> cb=[โ€ฆ]> (most recent call last):
  File "test/test_stack.py", line 34, in job
    await foo()

This library

Stack for <Task pending coro=<job() โ€ฆ> wait_for=<Future pending cb=[<TaskWakeupMethWrapper โ€ฆ>()]> cb=[โ€ฆ]> (most recent call last):
  File "test/test_stack.py", line 34, in job
    await foo()
  File "test/test_stack.py", line 38, in foo
    await bar()
  File "test/test_stack.py", line 42, in bar
    await baz()
  File "test/test_stack.py", line 46, in baz
    await leaf()
  File "test/test_stack.py", line 50, in leaf
    await asyncio.sleep(1)
  File "/โ€ฆ/asyncio/tasks.py", line 568, in sleep
    return await future
  File "<Sentinel>", line 0, in <_asyncio.FutureIter object at 0x7fb6981690d8>: โ€ฆ

Dependency Graph

References

https://mail.python.org/archives/list/[email protected]/thread/6E2LRVLKYSMGEAZ7OYOYR3PMZUUYSS3K/

Hi group,

I'm recently debugging a long-running asyncio program that appears to get stuck about once a week.

The tools I've discovered so far are:

  • high level: asyncio.all_tasks() + asyncio.Task.get_stack()
  • low level: loop._selector._fd_to_key

What's missing is the middle level, i.e. stack-like linkage of what is waiting for what. For a practical example, consider:

async def leaf(): await somesocket.recv()
async def baz(): await leaf()
async def bar(): await baz()
async def foo(): await bar()
async def job(): await foo()
async def work(): await asyncio.gather(..., job())
async def main(): asyncio.run(work())

The task stack will contain:

  • main and body of work with line number
  • job task with line number pointing to foo

The file descriptor mapping, socket fd, loop._recv() and a Future.

What's missing are connections foo->bar->baz->leaf. That is, I can't tell which task is waiting for what terminal Future.

Is this problem solved in some way that I'm not aware of? Is there a library or external tool for this already?

Perhaps, if I could get a list of all pending coroutines, I could figure out what's wrong.

If no such API exists, I'm thinking of the following:

async def foo():
    await bar()

In [37]: dis.dis(foo)
  1           0 LOAD_GLOBAL              0 (bar)
              2 CALL_FUNCTION            0
              4 GET_AWAITABLE
              6 LOAD_CONST               0 (None)
              8 YIELD_FROM
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

Starting from a pending task, I'd get it's coroutine and:

Get the coroutine frame, and if current instruction is YIELD_FROM, then the reference to the awaitable should be on the top of the stack. If that reference points to a pending coroutine, I'd add that to the "forward trace" and repeat.

At some point I'd reach an awaitable that's not a pending coroutine, which may be: another Task (I already got those), a low-level Future (can be looked up in event loop), an Event (tough luck, shoulda logged all Event's on creation) or a dozen other corner cases.

What do y'all think of this approach?

Thanks, D.

awaitwhat's People

Contributors

africanbeat avatar dimaqq avatar frankyang0529 avatar lee-w avatar puneethnaik avatar xingularity avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

awaitwhat's Issues

Show asyncio.shield in the traceback

1a2e438 can traverse asyncio.shield, i.e. show what's behind the shield that's blocking a given task.

This ticket is to explicitly show shield as an element in the task traceback or as a separate graph node.

Traverse Future's

When a task is (logically) blocked on a Future, it's actually blocked on a FutureIterator.

A simple Future may block N tasks, through N iterators.

We should figure out how to get from FutureIterator to the Future itself, so that, if 2 tasks are waiting on the very same thing, it's reflected in the graph.

Current state:

Task 1 โ†’ FutureIterator โ†’ ?
Task 2 โ†’ FutureIterator โ†’ ?

Desired state:

Task 1 โ†’ FutureIterator โ”ฑโ†’ Future
Task 2 โ†’ FutureIterator โ”›

Integrate with CI & docs

I'm thinking of integrating this repository with CI (Travis, Jenkins, CircleCI, etc) and readthedocs. For each commit to master, we will run the tests and update the documentation with the latest generated svg files.

Sleep test is broken

stack should be a -> b -> sleep(...) -> sentinel

but we're only getting a -> sentinel

[red herring] somehow, _what reports that stack pointer is null?

stack stops on AttributeError: '_asyncio.FutureIter' object has no attribute 'cr_frame'

๐Ÿคทโ€โ™‚

Dump report on signal

A helper function that dumps task state on a UNIX signal.
My fav. signal is WINCH.

It's probably easier to develop like this.

For comparison current approach is to spawn a dummy task whose purpose is only to report useful tasks's state:

t = asyncio.create_task(test())

Q: future of this project

What concerns me

  • doesn't compile for Py 3.10 (frame struct was changed), fixable
  • no users (via cursory github search)

Option 1

Work with cpython core devs to decide if it's a good idea, and if so, integrate the core feature (suspended task coro stack traversal) into cpyhton itself

Option 2

Abandon ship, give up

Option 3

Your idea, please?

Show asyncio.sleep

Show remaining time in asyncio.sleep (when some task is blocked on it).
Maybe show original sleep time too (?)

awaitwhat weird output on sleep loop

I'm using python 3.7.6 (AMD64) on MacOS, I used version (baa4c73)

I took the examples/test_signal.py changed it to a while loop, and changed the signal, see below:

--- a/examples/test_signal.py
+++ b/examples/test_signal.py
@@ -12,10 +12,10 @@ async def job():


 async def foo():
-    signal.alarm(1)
-    await asyncio.sleep(1)
+    while True:
+        await asyncio.sleep(1)


 if __name__ == "__main__":
-    awaitwhat.helpers.register_signal(signal.SIGALRM)
+    awaitwhat.helpers.register_signal(signal.SIGINFO)
     asyncio.run(main())

When I'm sending SIGINFO to the process, the output is the following image:
image

Is this intended? How should I interpret this image?

update readme



    Hi group,

    I'm recently debugging a long-running asyncio program that appears to get stuck about once a week.

    The tools I've discovered so far are:

        high level: asyncio.all_tasks() + asyncio.Task.get_stack()
        low level: loop._selector._fd_to_key

    What's missing is the middle level, i.e. stack-like linkage of what is waiting for what. For a practical example, consider:

    async def leaf(): await somesocket.recv()
    async def baz(): await leaf()
    async def bar(): await baz()
    async def foo(): await bar()
    async def job(): await foo()
    async def work(): await asyncio.gather(..., job())
    async def main(): asyncio.run(work())

    The task stack will contain:

        main and body of work with line number
        job task with line number pointing to foo

    The file descriptor mapping, socket fd, loop._recv() and a Future.

    What's missing are connections foo->bar->baz->leaf. That is, I can't tell which task is waiting for what terminal Future.

    Is this problem solved in some way that I'm not aware of? Is there a library or external tool for this already?

    Perhaps, if I could get a list of all pending coroutines, I could figure out what's wrong.

    If no such API exists, I'm thinking of the following:

    async def foo():
        await bar()

    In [37]: dis.dis(foo)
      1           0 LOAD_GLOBAL              0 (bar)
                  2 CALL_FUNCTION            0
                  4 GET_AWAITABLE
                  6 LOAD_CONST               0 (None)
                  8 YIELD_FROM
                 10 POP_TOP
                 12 LOAD_CONST               0 (None)
                 14 RETURN_VALUE

    Starting from a pending task, I'd get it's coroutine and:

    Get the coroutine frame, and if current instruction is YIELD_FROM, then the reference to the awaitable should be on the top of the stack. If that reference points to a pending coroutine, I'd add that to the "forward trace" and repeat.

    At some point I'd reach an awaitable that's not a pending coroutine, which may be: another Task (I already got those), a low-level Future (can be looked up in event loop), an Event (tough luck, shoulda logged all Event's on creation) or a dozen other corner cases.

    What do y'all think of this approach?

    Thanks, D.



It appears that the initial issue you mentioned, experiencing a stuck process once a week, could be related to disk I/O problems. In my experience, regular disk checks, like the ones my NAS performs on Saturdays, can cause significant delays and even crashes if there are processes running that heavily rely on disk I/O. This issue is also compounded by high user activity on the disks.

Regarding your async implementation, it sounds like you might benefit from considering threading if you need a higher level of parallelism. However, if you are tracking your tasks in a linear manner, creating a sequential flow of events through your async functions using await statements can work effectively. By properly ordering the dependencies with await, you ensure the tasks execute in the desired sequence.

In situations where you have a large number of tasks that can be completed mostly in parallel but have blocking dependencies, it's crucial to order those dependencies correctly using await statements, making sure the conditions are within the scope of your functions.

To troubleshoot potential bottlenecks and optimize your async program, consider incorporating clever insights for creating await points. Techniques such as using decorators to log await points, setting up breakpoints with a debugger, or implementing custom tracing functions can help identify and analyze the execution flow of your tasks.

By gaining a deeper understanding of the await points and their relationships, you can effectively troubleshoot and resolve any performance issues, including potential disk-related delays.

I'll be happy to share insights over a beer.

edit:
was curious if the readme was up to date and this got fixed

Split graph output into JSON + actual graph

Currently the task dependency graph is generated in one go.

It would be awesome to split this in 2 stages:

  1. generate plain data (nodes, edges, text)
  2. convert plain data into a dot graph

When that's done, it becomes possible to:

  • write tests over 1. :)
  • output 1. as JSON
  • output 1. as plain text, like Python traceback

guts:

stops = {t: blockers(t) or [f"<Not blocked {random.random()}>"] for t in tasks}
nodes = set(sum(stops.values(), list(stops.keys())))
nodes = "\n ".join(f"{id(t)} {describe(t, current)}" for t in nodes)
edges = "\n ".join(
f"{id(k)} -> {', '.join(str(id(v)) for v in vv)}" for k, vv in stops.items()
)

Cleanup build

What's required to build awaitwhat in a python:alpine container:

  • apk update
  • apk upgrade (dunno)
  • apk add build-base (to get gcc)
  • apk add libffi-dev (I guess I get it?)
  • apk add openssl-dev (why?)

Show network sockets

Event loop maintains a set of sockets that are being waited for with callbacks.
It should be possible to figure out which task is waiting for which socket.
Sockets could be reported with their type, state, direction (e.g. TCP, connected to 1.2.3.4:8080, read).

Traverse asyncio.wait

asyncio.wait is similar to asyncio.gather but supports more functionality:

return_when (operation mode) can be "first completed" "first exception" or "all completed".

we should report the operation mode and what's still pending behind wait.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.