diff --git a/io-between-turns/SKILL.md b/io-between-turns/SKILL.md new file mode 100644 index 0000000..f1c2b55 --- /dev/null +++ b/io-between-turns/SKILL.md @@ -0,0 +1,53 @@ +--- +name: io-between-turns +description: I/O between turns — use when writing or reviewing code that reads or writes files, sockets, databases, or other shared external state; when choosing between sync and async APIs (readFileSync vs fs/promises, DatabaseSync, blocking calls); when someone says "sync I/O is a pointy instrument" or "I/O should be done between turns". Sync access is fine only for state that is local, prompt, and private to the one actor (e.g. a Durable Object's SQLite). +--- + +# I/O between turns + +Event-loop concurrency (E, JavaScript, Workers) runs one **turn** at a +time: deliver one event, run the resulting calls to completion, then +take the next. A turn that never waits on the outside world is +isolated: nothing else can change what it can see while it runs. I/O +on shared or slow resources belongs **between** turns: issue it, end +the turn, and continue when the result arrives as a later event. + +> "Execution proceeds by taking a pending-delivery from the queue, +> delivering its message to its object, and processing all the +> resulting immediate-calls in conventional call-return order. This is +> called a turn. … Because each event's turn runs to completion before +> the next is serviced, they are temporally isolated." +> — [Concurrency Among Strangers: Programming in E as Plan Coordination](https://agoric.com/assets/pdf/papers/concurrency-among-strangers.pdf), +> Miller, Tribble & Shapiro, TGC 2005, §5.2 + +> "The actual E system does provide synchronous file I/O operations. +> When these files are local, prompt, and private to the vat accessing +> them, this does not violate turn isolation, but since files may be +> remote, non-prompt, or shared, the availability of these synchronous +> I/O operations does violate the E model." +> — same paper, §6, footnote 5 + +(Mark Miller's thesis, *Robust Composition*, 2006, builds on this +paper.) + +## the rule + +- **Shared, remote, or slow state → async, between turns.** The + filesystem, the network, other processes' databases: use promise + APIs (`node:fs/promises`, `fetch`) and `await` them. No + `readFileSync`/`writeFileSync`, no blocking calls inside a turn. +- **Local, prompt, and private to the actor → sync is fine.** A + Durable Object's SQLite (`ctx.storage.sql`) is reachable only by its + own DO, so it is "more or less more RAM": synchronous access keeps + turn isolation. +- **Only a sync API exists, for shared state?** (e.g. `node:sqlite`'s + `DatabaseSync` on a file other processes may touch.) Keep it at the + program's edge and say why in a comment, so it doesn't read as a + precedent. + +## see also + +- [ocap-discipline](../ocap-discipline/SKILL.md): who gets to do I/O at + all (authority passed in, not ambient); this skill is about *when*. +- [silent-failures-are-evil](../silent-failures-are-evil/SKILL.md): an + awaited I/O failure still needs a trace.