Skip to content

Add initial draft of Rust API design document - #45

Open
emmatyping wants to merge 16 commits into
3.x-rust-in-cpythonfrom
emmatyping/rust-api-design-doc
Open

emmatyping wants to merge 16 commits into
3.x-rust-in-cpythonfrom
emmatyping/rust-api-design-doc

Conversation

@emmatyping

@emmatyping emmatyping commented Aug 19, 2026 •

Copy link
Copy Markdown

Okay, it took a while to play with these API design ideas and get them to a state where I was happy with them. I have a rough prototype similar to #41 that is based on this, but I figured it would make more sense to discuss the API design and make sure I haven't missed things or there isn't unsoundness in this design. It turns out it is just darn difficult to get thread-state passing right when you consider re-entrancy and multi-threading.

As mentioned in the document, my overall priorities in this design were:

  1. be sound on GIL and free-threaded builds;
  2. support every PEP 11 platform;
  3. approach equivalent C performance where safety permits;
  4. feel familiar to PyO3 and C API users; and
  5. use modern APIs such as PyModExport.

I think 3 is probably the furthest from achievable with this current design, we query the TLS quite frequently with this design, but I believe a lot of those queries could be cleaned up. 4 is really a matter of taste, but I tried to be somewhat similar while also trying to have as small an API surface as possible.

I also wasn't totally sure where to put this, perhaps it would go better in InternalDocs?

AI disclosure: I worked with Codex and Claude when iterating on this API and the content is a mix of writing me and the LLM. I've reviewed every word, but please let me know if there is anything unclear!

Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
@emmatyping
emmatyping force-pushed the emmatyping/rust-api-design-doc branch from b334292 to 4a6e800 Compare August 24, 2026 18:37

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a read-through and had a few comments questions and suggestions

Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md
Comment thread RUST_API.md Outdated
@emmatyping emmatyping mentioned this pull request Aug 30, 2026
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
@emmatyping

emmatyping commented Oct 15, 2026 •

Copy link
Copy Markdown
Author

Okay, big update! I have revised the API design doc significantly, re-working how thread states/contexts work, how exceptions work, and adding support for what I believe is a safe subinterpreters API. I've also responded to the great feedback so far (thank you to everyone who has commented!)

I'd greatly appreciate re-review as folks have time. Barring major issues I hope to bring this as the design to the core sprints to discuss with folks there.

@willingc willingc left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emmatyping I made some suggestions on the intro for greater clarity. Overall, the doc as you presented the other day looks sound.

Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>

@dimaqq dimaqq left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've played this spec back and forth against AI. Here's what it considers a possible race, though I feel this is a bit beyond my level of comprehension when it comes to the details:

sequenceDiagram
    participant E as Rust entry thread
    participant F as Finalizer

    alt Entry acquires first
        E->>E: Read admission = open
        E->>E: Increment its thread-state lease count<br/>without a safepoint in between
        E->>E: Publish ThreadContext
        F->>F: Stop the world (or hold the GIL)<br/>and recheck lease counts
        F-->>F: Sees lease > 0. Waits for entry to finish
    else Finalizer closes first
        F->>F: Stop the world (or hold the GIL)<br/>and recheck counts = 0
        F->>F: Close admission
        E->>E: Read admission = closed
        E-->>E: Reject entry. Publish no context
    end
Loading

@dimaqq

dimaqq commented Oct 16, 2026

Copy link
Copy Markdown

Explanation for the above: proposed faster per-thread lease scheme needs to prove it preserves the same exclusion as the public PyInterpreterGuard.

"acquisition reads the flag and increments the lease count without a safepoint between, while finalization stops the world and rechecks counts before closing admission" could be sound, not only if CPython guarantees that finalisation cannot pass it's check while an entry is getting paused.

The AI friend proposes that "the document should state explicitly that an attached thread cannot be counted as stopped midway through the lease-acquisition sequence, and that the finalizer closes admission only after that stop-the-world rendezvous and its final count recheck." Or in other words, stating "without not safepoint between" is good, but now please prove that read and increment is really an atomic operation or specify how it can be implemented so that it's really an atomic operation.

I've also debated the possible approaches, here are the three alternatives:

  1. use PyInterpreterGuard for each Rust entry: simple but there's a hot counter
  2. put closed and active lease count into a single atomic: not sure if that's available on all platforms
  3. the proposed per-thread lease counts: correctness depends on stop-the-world and resume procol

Now... it bugs me a little that the core spec gets so complicated, but then again so is free-threading. So maybe that's OK.

I do wonder though, if the spec implies "enhancing" cpython core thread state tracking for the first-party Rust code, what about the third-party Rust modules, or modules written in other languages? Should we export something here? Would that be a good idea?

@emmatyping
emmatyping force-pushed the emmatyping/rust-api-design-doc branch from 958e71a to db9f214 Compare October 16, 2026 03:27
@emmatyping
emmatyping removed the request for review from JacobCoffee October 16, 2026 03:27
@emmatyping

Copy link
Copy Markdown
Author

I've played this spec back and forth against AI. Here's what it considers a possible race

Thanks for poking at this!

acquisition reads the flag and increments the lease count without a safepoint between

Ah! The acquisition occurs while attached to the interpreter, so a stop-the-world pause cannot run until that thread detaches or finishes, and another thread cannot run a finalizer in the GIL-ful build.

I agree however that rephrasing the invariants there would be quite useful. I've pushed db9f214 with the wording change.

@encukou encukou left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This looks as sound as the C API will allow :)

Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md Outdated
Comment thread RUST_API.md
Comment on lines +1309 to +1310
- **Static builds.** Static builds register modules through `PyInit_*`
inittab entries, so `PyModExport_<name>` needs inittab support.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For info: I've started on this but not a very high priority for me. If someone wants to take this, let's coordinate.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely! Would love to coordinate on this, as it's obviously a blocker for Rust. The PyModExport_ interface is way more ergonomic for Rust.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Into the sprint list it goes :)

Comment thread RUST_API.md
Comment on lines +102 to +103
serve this role, because `_PyInterpreterState_Enable` resets `next_id` and
the main interpreter is always ID zero. For the same reason an interpreter

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PyInterpreterState_Enable resets next_id and the main interpreter is always ID zero.

Is that an issue to fix in CPython? I'm not sure if we baked this into public API.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I haven't checked. I feel like I spoke to someone who mentioned that this was intentional, but I should follow up on that. Changing this would be much better :)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would probably be Eric Snow, I'd like to hear the reasons. (In person if he's at the sprint, so I'll not tag him here now.)

Comment thread RUST_API.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants