Deep dive into threading, blocking, sync/async, scheduler internals (split from scztt quarks thread)

That’s not a bad idea. As I said in a different context, the only real difference in terms of internals between a Thread and a Routine seems to be that a “pure” Thread (like Process.mainThread) always has a nil parent. I’m not sure if anymore Threads that are not Routines are even created by sclang, besides Process.mainThread.

I’ll have to digest the rest of the stuff you wrote for a more complete response. I don’t know what GC perf impact making every REPL a new Routine would have.
But you could test this pretty easilty by spamming some, say 1000 cmds directly and also 1000 cmds wrapped in Routine.run { }. And see what GC impact it had. I don’t know how the evaluate the latter myself, at the moment, i.e. what GC internals of perf. counters to look at.

Worth noting that the above won’t allow OSC to be received though while the Routine executes, because of the global interp. lock, as I detailed in my previous post in this discussion thread. The routine would still have to defer the part that it wants to exec after the OSC response comes back. But you’ll at least fix the yields barfing at the user.

For the latter, I’m thinking that _Routine_yield and its two near-copypasta friends (that implement alwaysYield and yieldAndReset) would have to release the global interpreter lock iff there is no other Thread that can execute immediately.
They might even do that right now via the scheduler code, but I haven’t really checked that part of the code.

Scratch that. The per-REPL-cmd routine that you just started will have its parent main thread set as immediately executable. The per-REPL-cmd Routine would end up yielding to the main Thread. So the latter would have to give up the interpreter lock without returning something, which is presently not possible, as I understand it.