With regard to scaling coroutines, I guess this is basically the problem that Erlang tackles. Having zero shared state obviously solves the migration problem. (but raises other challenges)
How would one do a generic copy-on-write mechanism in Lua to automatically enforce zero shared state? (But only create copies once state has been changed.)
It would surely require some fairly severe changes in the Lua runtime. It's been 3-4 years since I touched Lua, but assuming they haven't changed that much, the data structures themselves are a fairly simple mix of tagged C unions with pointers to structs for arrays, objects etc.
You'd need to either run through all references in the current context and replace them with references to a mutable copy, possibly copying whatever is holding them as a result. This is horrible, and why you tend to use immutable, persistent data structures for this type of thing.
Alternatively, you'd need to somehow detach identity from memory location. Initially, object #123 is at the same position in all contexts, but if you mutate it, it'll be copied elsewhere. When you dereference #123 in that particular context, you will now be directed to the new location. This avoids the dependency update chain but adds an extra layer of indirection, which will presumably be a hashtable or so, which could impact performance quite severely and take up a lot of memory.
You'd also need to somehow tag and track objects which are shared (and need COW) or which can just be mutated directly.
In short, Erlang is probably the way it is for a reason. Trying to graft its model onto another language may well produce something hideous. Don't build your app on the assumption you'll be able to scale it later if your contexts share mutable state.
I don't think there's a silver scalability bullet in our future. You either start by assuming the worst case you can think of and restrict yourself to "safe" techniques, which possibly cause you to do a lot of mental contortion and work you wouldn't need to if you weren't going to scale. (this approach isn't infallible either - emphasis on the worst case you or the maker of your tools can think of) Or you take the approach of ignoring scalability for as long as you can get away with it, risking a complete rewrite when your design falls over. Or start somewhere in between, depending on your (predicted) needs and preferences.
Disclaimer: my dealings with Lua are some years back and weren't all that extensive. (I most vividly remember debugging a crash that was caused by the allocator running a GC pass under out-of-memory conditions, a feature someone had evidently added without checking it was safe) My knowledge of Erlang is passing at best.
I'm not sure you'd want to do that. Here's why:
It would surely require some fairly severe changes in the Lua runtime.
In VisualWorks Smalltalk, I'd just start off by setting objects immutable and using a top-level exception handler on the mutation exception. Alternatively, one could also use forwarder proxies.
This avoids the dependency update chain but adds an extra layer of indirection, which will presumably be a hashtable or so, which could impact performance quite severely and take up a lot of memory.
For building application servers, there should be a mechanism to handle this sort of eventuality efficiently. Such mechanisms are also very useful when building things like Object Relational frameworks. This may be counter to Lua's design goals towards being small and highly embeddable.
At the vm-local level (i.e., not fork -> process memory COW, but rather immutable & persistant objects), you can make objects immutable via their metatables: set __index and __newindex to functions that do proxying. The former would lookup fields from the appropriate object instance, and the latter would set the (internal) object reference to a new copy with the updated field.
Come to think of it, that's almost a direct Lua translation of what you said: "In VisualWorks Smalltalk, I'd just start off by setting objects immutable and using a top-level exception handler on the mutation exception. Alternatively, one could also use forwarder proxies." The __newindex metatable field is a mutation hook and __index is like doesNotUnderstand. (Lua seems more like Self than Smalltalk, though.)
But, I don't think I'd do it that way - when going for immutable & message-passing semantics, adding syntactic sugar to make it look like stateful OOP seems misleading to me. You can stream plain Lua tables* over sockets pretty easily, though, so message-passing is probably much simpler. You can stream Lua functions, but not coroutines. (Streaming functions with closures takes a bit of debug API hackery, though.)
* Lua tables are almost identical to JSON.
FWIW, there's a port of Erlang's message-passing concurrency and supervision hierarchy programming model to Lua: ConcurrentLua (http://concurrentlua.luaforge.net/). I've only toyed with it a little bit, since I didn't really know Erlang at the time. It uses co-operative multitasking (coroutines), has localized mutable state (doesn't force modify-once variables within processes), and doesn't have selective receive (yet, though I keep meaning to add that). I'd rather just use Erlang for fault-tolerant-server projects (possibly w/ Lua behind ports), but it seems like a good library for distributed programming in Lua.