Skip to content

Add R02, where the state lives - #180

Merged
tamnd merged 1 commit into
mainfrom
r02-where-the-state-lives
Sep 5, 2026
Merged

Add R02, where the state lives#180
tamnd merged 1 commit into
mainfrom
r02-where-the-state-lives

Conversation

@tamnd

@tamnd tamnd commented Sep 5, 2026

Copy link
Copy Markdown
Owner

R02 is the second of the nine runtime lessons, and it is the one that puts a frame around everything before it. A running Python is three nested things: a runtime that there is one of per process, interpreters inside it, and threads inside those. Every fact this book has taught you belongs to exactly one of those three levels, and the level decides who can see a change when you make one.

The method is the same in every section. Make a second interpreter, ask both of them the same question, and watch where the answers stop matching.

Eight cells. Nine objects asked for their address on both sides. A search for the exact integer at which the two interpreters stop agreeing. The interpreter list, with ids counting upwards and never coming back. The recursion limit, a warnings filter and sys.modules changed on one side and read on the other. signal.signal tried from the main thread, from another thread, and from inside a second interpreter. Two threads each holding a different exception at the same instant.

The facts worth keeping. The shared pile is small and it is not shared by luck: None, the booleans, the small ints, the one character strings, the fixed identifier strings and the static type objects are fields of the runtime struct rather than allocations, which is why nothing can free them and why every interpreter in the process points at the same bytes. Where the small int run ends is a #define rather than a policy, and it moved from 256 in 3.14 to 1024 in 3.15, which the cell measures rather than asserts. Everything else you think of as belonging to Python belongs to one interpreter: its own sys.modules, its own builtins, its own import lock, its own warnings filters, its own recursion limit. Signals are the exception, because the operating system has never heard of interpreters, so there is one handler table per process and the test for who may write to it is two conditions in one line of C, one for each of the two upper levels. And the exception being handled is per thread, which two threads inside an except block at the same instant make obvious.

The lesson also says out loud why no cell calls sys._current_frames from inside a second interpreter. On a build with the GIL that aborts the whole process, because the function materialises frame objects for other interpreters' frames and each interpreter allocates from its own pools. That is #179, found while writing this.

Two Tier 1 recordings, one on a release build and one on a free threaded build. An operating system thread takes a few hundred microseconds. An interpreter takes about fifty times that, and keeps a couple of megabytes of resident memory for as long as you hold it. The free threaded build charges more for both, and the reason is the allocator rather than noise: mimalloc heaps and obmalloc pools do not price an extra interpreter the same way.

Also in here: two new glossary terms, runtime state and static object, six diagrams, fifteen citations, and the R02 rows in both READMEs and in the experiments index. GLOSSARY.md is 218 terms and CLAIMS.md is 571 claims across 73 lessons.

Part of #27.

A running Python is three nested things: a runtime that there is one of
per process, interpreters inside it, and threads inside those. Every
fact this book has taught you belongs to exactly one of the three
levels, and the level decides who can see a change when you make one.

The lesson finds the boundaries by experiment rather than by assertion.
Make a second interpreter, ask both of them the same question, and watch
where the answers stop matching. Eight cells, twenty three in total,
fifteen citations, six diagrams and seven claims.

Two interpreters return the same id for None, the booleans, the small
ints, one character strings and the built in type objects, because all
of those are fields of the runtime struct rather than allocations, and
different ids for everything else. Where the small int run ends is a
define rather than a policy, and a cell finds the edge by asking both
interpreters for the address of every int in a range. It is 256 on 3.14
and 1024 on 3.15, which is declared on the cell.

An interpreter owns its own sys.modules, its own builtins, its own
import lock, its own warnings filters and its own recursion limit, so a
change to any of them is invisible next door. Signals are the exception:
one handler table for the whole process, with the main thread of the
main interpreter as the only writer, which is two conditions in one line
of C and is exactly what the error message says. A thread owns the
exception it is handling, and two threads inside an except block at the
same instant each see their own.

The lesson says out loud why no cell calls sys._current_frames from
inside a second interpreter. On a build with the GIL that aborts the
process, because the function materialises frame objects for other
interpreters and each interpreter allocates from its own pools. That is
issue #179, found while writing this.

Two Tier 1 recordings put a price on it, one on a release build and one
on a free threaded build. An interpreter costs about fifty times what an
operating system thread costs to make, plus a couple of megabytes to
keep, and the free threaded build charges more for both because mimalloc
heaps and obmalloc pools do not price an extra interpreter the same way.

Also adds two glossary terms, runtime state and static object, and the
R02 rows to both READMEs and to the experiments index. GLOSSARY.md is
218 terms and CLAIMS.md is 571 claims across 73 lessons.

Part of #27.
@tamnd tamnd added this to the M8 Concurrency and runtime milestone Sep 5, 2026
@tamnd tamnd added kind/lesson A chapter: prose, notebook, experiments, boss fight area/concurrency The GIL, free threading, subinterpreters and asyncio area/runtime Startup, shutdown, import, extension modules and the C API labels Sep 5, 2026
@tamnd
tamnd merged commit 7177c2d into main Sep 5, 2026
16 checks passed
@tamnd
tamnd deleted the r02-where-the-state-lives branch September 5, 2026 18:40
@tamnd tamnd mentioned this pull request Sep 5, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/concurrency The GIL, free threading, subinterpreters and asyncio area/runtime Startup, shutdown, import, extension modules and the C API kind/lesson A chapter: prose, notebook, experiments, boss fight

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant