Advanced

Reading a stalled run

A stall has evidence before it has a fix. The tools here name the task that is open, which is the sentence a fix needs.

What you will see

fetch

About 2 min of reading

Try It

Check what the tools report.

1

Thread listed.

import threading print(len(threading.enumerate()))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
1
2

Thread ended.

import threading t = threading.Thread(target=lambda: None) t.start() t.join(0.1) print(t.is_alive())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
False
3

Stack present.

import traceback print(bool(traceback.format_exc()))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
False
4

Task named.

import asyncio async def main(): t = asyncio.create_task(asyncio.sleep(0), name="fetch") await t print(t.get_name()) asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
fetch

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

The run is stamped, the open task or thread is named, the stack names the wait, and the report carries the task name.
1 stamp each stage
↓
2 list the open work
↓
3 read the stack
↓
4 name it in the report
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

import threading sets nothing on screen
print(len(threading.enumerate())) 1
Why this works
Name the open work

Every fix starts with a sentence about what is open and where it waits. The tools above produce that sentence, and a guess does not.

Cheap stamps first

A stamp around each stage costs nothing and finds most stalls. Reach for a profiler only after the stamps name the stage.

Examples

Ask what is open

import asyncio

async def main():
    await asyncio.sleep(0)
    print(asyncio.current_task())

asyncio.run(main())

The current task names the body that is sitting in a wait. That name is the starting point.

A thread dump

import threading

threads = threading.enumerate()
print(len(threads))

enumerate lists the live threads. A thread that never ends is the one holding the stall.

A join that waits

import threading

t = threading.Thread(target=lambda: None)
t.start()
t.join(0.1)
print(t.is_alive())

A join with a timeout plus is_alive turns a hang into a printed fact about one thread.

A stack to read

import traceback
print(bool(traceback.format_exc()))

A stack names the line inside the wait. The line is what tells you which wait has no limit.

A timer in the log

import time
print(int(time.time()) > 0)

Stamp the log around each stage. The gap between two stamps is the stage that waits.

A name per task

import asyncio

async def main():
    t = asyncio.create_task(asyncio.sleep(0), name="fetch")
    await t
    print(t.get_name())

asyncio.run(main())

Name the tasks. A report that says fetch timed out is actionable, one that says Task is not.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← A limit on a wait
Previous Lesson
A limit on a wait
Next Lesson
Where the boundary sits
Where the boundary sits →