Pitch: Add an Actor Reentrancy section to TSPL

Hi all — I'm Alex Apostol, currently in the Swift Mentorship Program, mentored by @maartene. As my mentorship task, I'm working through the open-source contribution flow for Swift, and Maarten and I landed on documentation as a good first target.

While reviewing the Concurrency chapter, we noticed TSPL doesn't currently document actor reentrancy — the fact that an actor can be reentered by another call while a previous call is suspended at a potential suspension point. Reentrancy is a deliberate design decision from SE-0306 "Actors".

SE-0306 dedicates a full section to justifying it (avoiding deadlocks, keeping actors responsive) and even flags this exact caching pitfall in passing — "two clients might ask for the same image URL at the same time, in which there will be some redundant work" — without spelling out how to avoid it. None of that made it into TSPL's prose: there's only an unpublished author comment mentioning "reentrant code" in an outline block after the Global Actors section, no real prose covering it, and no open PR addressing it either.

I've drafted a section that:

  • Defines reentrancy and states that it's the default for every actor
  • Explains why it exists — so actors stay responsive, and so two actors awaiting each other don't deadlock
  • Walks through three separate pitfalls:
    • A caching actor that duplicates work because a reentrant call runs before the first has written its result (wasteful, but not actually wrong on its own — though at scale, that redundant work compounds into a cache stampede/thundering herd, which is a real production failure mode, not just an inefficiency)
    • A price cache where two reentrant fetches for the same symbol can return different prices if the underlying value moved between them, and if the older response happens to write last, it silently overwrites the newer one — corrupting the cache with a stale value indistinguishable from a valid one.
    • A game's Player actor, where two reentrant, individually-lethal attacks both pass an isAlive check before either has applied its damage, so both get credited with the killing blow

Full draft here: Actor Reentrancy — TSPL pitch draft · GitHub

This update will be a new section in TSPL's Concurrency chapter (TSPL.docc/LanguageGuide/Concurrency.md), inserted right after "Actors" and before "Global Actors". Open to feedback on a few things:

  • Whether that's the right insertion point.
  • The gist walks through three pitfalls, as previously mentioned:
    • That's three separate worked problem-and-fix pairs in one section, which is a lot — I'd genuinely like feedback on whether all three earn their place, or whether it should be trimmed.
  • The fix section resolves all three pitfalls, and each one now uses a genuinely different technique:
    • The caching actor records the in-progress work as a Task before the first await, so a reentrant call shares that result instead of racing its own.
    • The price cache holds a lock around the entire method via a small actor-based mutex, trading away some concurrency for correctness.
    • The Player fix commits the whole outcome synchronously before the only await, so there's nothing left to race. None of the three is a restatement of another, so I'm fairly confident all three earn a full worked example rather than a shorter pointer back to an earlier one.

One more thing, upfront: I used an AI assistant (Claude) to help with wording, structure, and proofreading this post and the linked section, and to check the draft against swift-book's style guide. Wanted to be upfront about that since AI use is a live topic here — happy to go into more detail on where/how if that's useful for reviewing the pitch.

Thanks for reading!

10 Likes

Hi there, thanks for picking up the topic - it would be good to document better for sure.

I'll give the edits a read. It would be much easier to comment on if you made it into a draft PR -- could you do that so it's easier to leave inline review comments?

I saw a few places already where I'd like to leave comments but it's hard to quote the sentences and point out "fix that one" over here on forums rather than in the doc directly.

I don't think I would be comfortable showing AsyncMutex as "the" solution tough... I think we might be better off showing various ideas how to solve it, but not get too tempted by showing entire implementation types. As that begs the question, why not ship such type? :slight_smile: And we don't for good reason, if all you do is a mutex, use a Mutex... So this section will need more thinking.

On a separate note I don't know if we'd be allowed to use these images currently in the draft, @Alex_Martini may need to chime in on that?

Thank you for transparency and careful use of AI tools, it's definitely fine to use to help out, but it's important we keep the prose very precisely worded with no space for AI made up stuff, thank you!

4 Likes

Hello Ktoso, thanks for your time and for your reply.

I need some clarification on for this points.

For this question:

¿ I saw a few places already where I'd like to leave comments but it's hard to quote the sentences and point out "fix that one" over here on forums rather than in the doc directly.?

What tool is usually used for this kind of feedback? Would a repo on Github and working throughout pull requests be a solution?

As for the second question I'm also no sure i understand them correctly. ¿I think we might be better off showing various ideas how to solve it? In the gist there are three different solutions.

What do you meen by: ¿As that begs the question, why not ship such type?

About the images: they are custom and i used AI to generate them, i have the source code since they are made in html and exported to SVG. I'm not sure how the copyright rights work. But open to whatever you need to distribute them or give full rights to anyone.

Thanks you for your time again. And sorry for so many questions.

Hi Alex,

Opening up a draft PR on the swift-book repo would be the most direct way to get feedback - that uses GitHub's review setup, and can work fairly well. The basic flow is:

  1. Make your own fork of the swift-book repository on GitHub (alexapostol/swift-book).
  2. Make a branch off the latest (main), then update the markdown akin to what you have in the Gist today, then push that branch to your fork
  3. Open a draft PR from your branch on your fork, aimed at "main" on swiftlang/swift-book. (when you open a PR, you can open it directly, or open it as "draft" - and the "draft" status is a handy indicator that you're still working or iterating on the content.

(apologies if this process is all super-obvious - I figured it'd be better to explain stuff you already knew than assume)

One trick I'd suggest (and I think it's mentioned in the TSPL style guide) is to really lean on semantic line breaks - basically - make sure each sentence has it's own line, and for a really long sentence, drop in a newline to break it over two or three that aligns with where you'd normally drop in punctuation - comma, semicolon, etc.

The diagrams are potentially trickier - There are relatively few diagrams in TSPL today, and yours (I'd call them "railroad diagrams") are effective at showing the layering of time and lines, although I'd want to ensure that we're clear on the copyright. If you hand-created the diagrams, that's pretty direct. There's also the detail that when we do put diagrams into DocC content (like TSPL), we typically include both a "light" and "dark" version, and we want to keep to a style that matches the other diagrams. Caveat that I'm not a designer, but I'd recommend leaning more into no background and line-art styling over heavy, bold colors that you're currently using, to keep them more closely aligned with diagrams that are already in TSPL. Also, remember we'll want/need alt-text for any diagrams, so keeping them as simple as possible will have additional benefit in fully describing them for screen readers.

This is all a bit of a new space as we're moving more of this content into open source, so I think we'll need to do some checking with legal to understand what's acceptable.

4 Likes

Hello @Joseph_Heck i have opened the PR. Its a draft for so we can discuss there further improvements on it.

1 Like

Hi Alex,

I think you need a different example for your section ‘A stale overwrite’. It seems to me this isn’t a reentrancy problem but a caching problem. Assume the two calls in the example happen one after another with no reentrancy whatsoever. The second call will get the stale stock price because the first call cached the first price and every subsequent call will get that price since there is no cache timeout or that a changing stock price invalidates the cache say via some registered callback to the actor.

1 Like

Better to keep it simple. Demonstrate the problem and solve it with a killer example. :slight_smile: