I'm really excited about async programming in what Zed dubs the "natural style": using coroutines to suspend and resume processing at points where input is needed.
Can this eliminate the callback hell that plagues the explicitly async frameworks (node.js, jquery, twisted, etc)?
But what about the downside? Now your handler is stateful and the language runtime is taking care of suspend / resume of the handler code. Can you migrate the suspended coroutine to another process, possibly on another machine? Or do you just take care to use sticky load balancing for all your stateful routes?
HN does something similar right? My understanding is that the fnid=... query param identifies a closure that's serving up a pagination of stories, and the "Unknown or expired link" error message happens when your own personal closure gets garbage collected by Arc.
In both the coroutine and closure cases, you've got something on the server side that's consuming resources even when there are no requests. This seems like a problem for scaling.
Scaling and session affinity are the big concerns with Coroutine and Continuation based web based solutions.
Sticky load balancing is the norm (http://continuity.tlt42.org/Performance_and_Scalability) but it means a running Coro/Continuation can remain in memory for quite a while (Seaside saves continuation (or it did) to disk after some idle time).
Still I love using these kind of libraries / frameworks especially for smaller web apps.
The interesting function which does all the work is:
(define (start req)
;; First, we get input from the user
;; Then, we pause for the link
;; Finally, we show what the user typed.
(let ([input (get-input-from-user)])
(pause-for-link)
(show-input input)))
Each of these calls - get-input-from-user, pause-for-link, and show-input sends a complete page back to the user and waits for them to click a link or submit a form request. It's a really smooth way of solving some problems.
>Instead of one gigantic process that has to handle all of an application, you'd have a handful of little ones. Instead of scaling massive applications across a cluster, you just scaled each interface out as needed. Instead of upgrading the whole enchilada at once, you just upgraded each interface only if it needed it. Instead of worrying about migrating user's coroutines across upgrades you just did the "Erlang trick" and moved them over to the new deployments.
He goes on to describe Tir, the natural style, and Lua's coroutines. Great intro to a fun framework.
Instead of worrying about migrating user's coroutines across upgrades you just did the "Erlang trick" and moved them over to the new deployments.
I'd like to hear how this works in detail. Suppose a user is exercising a multi-request handler, say pagination of personalized results.
At what point do you kill the coroutine and let the next request start a new handler in the new, upgraded process? What about state that the coroutine may have that hasn't been persisted anywhere else?
> Can this eliminate the callback hell that plagues the explicitly async frameworks (node.js, jquery, twisted, etc)?
But what about the downside? Can you migrate the suspended coroutine to another process
No, Lua coroutines only provide language or syntax level concurrency. They make routines more stateful, which can be a benefit if you want to refer to the same local variables and scope in between yields, but do have the downside of incentivizing routines to be larger in size (although you rely on fewer of them).
Also, the use case for coroutines is really limited to expressing lists of serially occurring tasks. This can work well for request-response style applications. But if for instance, you need to be able to read from, write to, and wait for timeouts on the same socket simultaneously (say a chat room), it would be a bit silly to spawn 3 coroutines per client (or 1 with heavily branching code), and use unnecessary overhead than simply binding and dispatching function calls for events without using a coroutine.
Regarding dispatching function calls for events, this can also be done in a variety of ways without creating a soup of nested lambdas. For instance, local functions or object methods. This also avoids the instantiation of additional function values (lambdas) for each client, which doesn't really slow things down in Lua but uses up more memory.
Scaling isn't so much the problem, since you can do various things to keep users on specific servers. In my setup, I also solve a lot of the state issues since there isn't a single state for the entire app, but instead for each process that runs a small part of the app. That means you can scale and change up the application easier if you need without impacting the other parts.
The real problem with coroutines for storing state is simply that upgrading the code requires you to either kill all the old state, or trickle users off on A/B paired processes. Because the state is tied to the code, you can't just change the code and hope it works. Everything will be off and the coroutines won't continue.
Since Tir uses tiny processes, it's easier to do these upgrades in one part without killing the other parts, and since users are never in any one process for very long by design the risk is smaller.
But, you definitely have to be more careful than the other models.
...since users are never in any one process for very long by design the risk is smaller.
You might design the interactions to be brief, but how do you actually enforce that when you need to upgrade or reclaim resources?
To make this more concrete, suppose you have a shopping cart. Sure it probably only takes most people 5 minutes to check out from start to finish, but what about the stragglers? Do you have a nice way of timing out the coroutine handler due to inactivity, and saving off state when that happens?
Or do you just solve it the HN way ("Unknown or expired link," hmm what does that even mean).
This isn't really a Tir feature, but do you hate when there's bugs in your core libraries and that guy who "owns" the broken library refuses to fix it? Me too, that's why Lua and LuaRocks are awesome. You get a tight core language that's completely described in_a_few_HTML_pages and then install all the platform libraries you need with LuaRocks. No more gatekeepers with Lua.
"No more gatekeepers" seems to be a theme with Zed's contributions of late. (And quite possibly over a longer time span.) In the case of Lua, the role of "core" and the "glue" are both a particular small and fast language. Because "core" is so small, any "benevolent dictators" in this environment have a small footprint. (Yes, there's a "small government" analogy here, but let's leave the politics out.)
The price of this is library fragmentation. There's more than one approach to objects, for example.
I wonder if there's a middle ground? Node.js seems successful in promoting a community standard of no blocking code.
>The price of this is library fragmentation. There's more than one approach to objects, for example.
I've found that to be the case with Lua in general. The language is pretty easy to hack with, and so there's alot of wheel making. Lua has a mantra of "provide mechanisms, not policies", and since there's no formal class system, and multiple ways to define modules it's almost fragmented by design.
I think the designers are working on a better module system for the next release, or did that make it into 5.2?
That used to be a problem before luarocks, but now with luarocks you don't have to worry about it. You get fragmentation anywhere you go in every langauge no matter what. The problem in other "batteries included" languages is the core team dictate to everyone else the primary libraries, and then many times they refuse to update them and you have to work around them.
In Lua, you don't have to do any of that. Lua gives you the kernel, then you add everything else you need.
My big problem with most frameworks is that any moderately sized application becomes excruciatingly monolithic. With Rails, the idea is that you have more code reuse, but then this becomes booting the whole of the universe to do something simple. Most of the time, your feature only needs a model, or a tiny part, of the application to do it's thing.
With Tir, you have a bunch of small parts working together. You can reuse the code, but the process only needs to load the parts it needs to work with. The implicit nature of rails really hurts it's ability to scale a particular part of the application without having to install the entire application.
An example, you scrape twitter for mentions of your app and shove them on your homepage. Twitter rate limits, so you background job this and run it in an interval. In the rails world, this means a resque / utility server, running the same code, each working booting up the full app, same deps, same model.
In Tir, there's no reason to do that. Create workers, have them load only the needed bits to access twitter and your model, make updates. Very light weight.
However:
PUB/SUB for workers? Really? Can't we get mongrel2 style work N:M (PUSH <- PULL, PUB <- SUB) distribution? This way we at least have an assurance that only one worker will get the job.
Same here. The framework is interesting, but having zedshaw in the title makes it more so since many of us are interested in knowing what he comes up with.
Yes, Nagare (http://www.nagare.org) is a continuation-aware Web framework with the same components model than Seaside.
Python Stackless is used to take continuations and also to serialize all the states, which can be stored, by configuration, in memory or to an external Memcached server. With the later setting, all the continuations can freely migrate across the machines of a cluster.
If, then wait a bit, at the moment he is focusing and having a lot of fun writing books. We can expect (or hope) to have him back to the code in a couple of months :)
Look at brubeck (mentioned here). It doesn't support the different state management styles since Python's coroutines are broken, but it does most of it.
Warning folks: Since I've been working on my books I haven't had time to hack on this lately. I will be picking it back up when I have a break from the books to code up a few ideas I have.
Can this eliminate the callback hell that plagues the explicitly async frameworks (node.js, jquery, twisted, etc)?
But what about the downside? Now your handler is stateful and the language runtime is taking care of suspend / resume of the handler code. Can you migrate the suspended coroutine to another process, possibly on another machine? Or do you just take care to use sticky load balancing for all your stateful routes?
HN does something similar right? My understanding is that the fnid=... query param identifies a closure that's serving up a pagination of stories, and the "Unknown or expired link" error message happens when your own personal closure gets garbage collected by Arc.
In both the coroutine and closure cases, you've got something on the server side that's consuming resources even when there are no requests. This seems like a problem for scaling.