Language path · Python · 49 layers

Python: types, asyncio, and the GIL

if __name__ wraps the app. The app calls Handler. Handler may create_task a worker. The contract — names, lists, exceptions — links into Handler. Pan down: concurrency catalog, then the loop as a pipeline, with the traps crossed out. Click Handler, Accept, or if invoice:.

Click a shape or a question

Questions this tree answers

A request · one Task per accept · cancel is not optional

Types · lists · exceptions
names · objects
lists · dicts
except · with · venv
__main__ → app → handler
Operation
Library
Methods · protocols
GIL · one Task per request
Call frame
Heap / refcount
list header
PyObject header
GC / cycles
Concurrency catalog
asyncio · bound
queue · process
cancel · lock
typing · io
Loop — pipeline and traps
Task created → drain
don’t · advanced last
process wrapsoperationlibrarydon’t extendmust still enddon’t import-runvs class statesatisfiesruns onholdsretainif unreachablevs threadsvs one bitDoes not: subclassAllows: write responseAllows: task after commitAllows: library save3. types2. exceptlistsAllows: selfDoes not: class cacheAllows: create_taskDoes not: wait forever
entryif __name__ == "__main__"
  • runtime dials after import
rejectedclass Api(FastAPI)
  • is-a · not the model
  • don’t subclass
routerFastAPI / APIRouter
  • routes · two children
  • Handler + library · not nested
operationasync def create
  • validate · save · return
  • one Task for this request
writereturn Response
  • status then body
  • no extra Task
side pathcreate_task fanout
  • after commit
  • must still cancel
libraryinvoice.save
  • domain · not main
  • tests import this
  • sibling of Handler, not a child
runtimepython / uvicorn starts
  • after import
  • you do not dial
wrongside effects on import
  • not how a process starts
  • tests call the library
noneNone · True · 0 · ""
  • bound is valid
  • unbound is NameError
bindx = v · LEGB
  • name → object
  • inner x = is a new local
identityis · == · id()
  • same object vs equal
  • None with is
trapdef f(xs=[])
  • one list for every call
  • None + assign inside
recordclass Invoice
  • __init__ · __dict__
  • names alias objects
  • methods hang off this
headerlist · pointers + overalloc
  • array of refs
  • slice copies pointers
growxs.append(x)
  • returns None
  • += mutates a list
fixed(a, b) · comma
  • structure, not a grow API
  • (1,) needs the comma
hashd[k] · .get
  • KeyError vs None
  • not a concurrent map
textstr · bytes · encode
  • len is characters
  • bytes at the edge
  • immutable
defdef f(a, *, k=None)
  • *args · **kwargs
  • hints are not runtime
LIFOwith lock:
  • enter now · exit on leave
  • async with in async
pathexcept InvoiceError
  • not a return value
  • visible branch
causeraise Wrap() from e
  • __cause__ walks
  • bare raise keeps tb
forbiddenexcept: pass
  • hides KeyboardInterrupt
  • map, don’t hide
API__all__ · public names
  • convention · not capital
  • import is cached
tabledef test_save
  • parametrize · fixtures
  • import the library
identityvenv · lock file
  • isolate the interpreter
  • lock what you run
instancedef save(self)
  • mutates the same object
  • no value receiver
classcls vs shared state
  • class var is one for all
  • keep instance data on self
has-aclass Store(Protocol)
  • structural, not is-a
contractif it has save()
  • implicit · keep it small
trapif invoice:
  • empty is not missing
narrowisinstance(x, T)
  • except / match preferred
TaskTask · coroutine
  • frame · parked or running
  • not an OS thread
threadThread · OS
  • must hold the GIL for bytecode
  • I/O may drop it
GILGIL · one bytecode
  • serializes Python, not your invariant
  • not a Task cap
framecreate() frame
  • locals die on return
  • unless something retains
  • recursion still capped
heapInvoice on the heap
  • always a PyObject
  • refcount zero frees now
  • cache keeps it live
headerob_item · size · allocated
  • pointer array
  • objects elsewhere
  • alias shares the list
headerPyObject { refcnt, type }
  • every value starts here
  • None is a singleton
  • calls go through type
eligiblerefcount 0 or cycle
  • zero frees now
  • a waiting Task still roots
measurepy-spy · tracemalloc
  • names the live set
  • don’t guess gc thresholds
startasyncio.create_task
  • new Task · small frame
  • must be able to end
boundSemaphore
  • max_workers
  • ceiling around tasks
joingather
  • wait all
  • does not bound
groupTaskGroup
  • first error
  • cancels siblings
  • gather + cancel
meetasyncio.Queue
  • put waits when full
  • maxsize is the cap
I/Othreading.Thread
  • GIL · overlap I/O
  • join them
CPUProcessPoolExecutor
  • pickle the args
  • new interpreter
yieldawait
  • gives the loop a turn
  • missing await never ran
proverbPass on a Queue
  • ownership moves
  • lock if already shared
requesttimeout / cancel
  • CancelledError at await
  • don’t store tasks on the app
lockasyncio.Lock
  • async with
  • not across blocking I/O
bitasyncio.Event
  • one bit
  • not a job queue
bridgeasyncio.to_thread
  • blocking call leaves the loop
  • not for CPU forever
hintslist[T] · Protocol
  • checker, not runtime
  • not monomorphized
record@dataclass
  • default_factory
  • frozen · slots
bytesopen · streams
  • with open
  • don’t block the loop
latelambda: i
  • lookup at call
  • freeze with default
1 createNew Task
  • create_task · small frame
2 readyLoop ready queue
  • coroutines that can run
3 executeRun until await
  • create() body here
4 wait I/Oawait socket / DB
  • task off the ready queue
5 GILGIL drop / switch
  • I/O and C may release
  • a Python loop does not
6 offloadto_thread / executor
  • blocking work leaves the loop
parkawait Event / Queue
  • off the ready queue
  • must wake · timeout
drainlifespan shutdown
  • stop accept · budget
  • no naked sys.exit
mapTask ≈ coroutine · Thread ≈ OS · GIL ≈ bytecode lock
  • create → ready → run → await → drain
  • GIL caps bytecode, not Tasks
forbiddencreate_task in a million loop
  • no ceiling
  • pool instead
forbiddencreate_task without cancel
  • leaks after the client goes
  • timeout or link it
undefinedtwo tasks · one dict · await between
  • GIL ≠ the invariant
  • check-then-act is a race
escape hatchctypes · cffi
  • lifetime is yours
  • almost never in Handler
runtime typesinspect.signature
  • decode lives here
  • not the hot loop
72%

Click a box · three arrows off a layer are what it allows

What Python is