First: reactor-based event loops aren't always a performance optimization. Round tripping I/O operations through an event loop adds latency. Node's approach is great if you have large numbers of mostly idle connections, such as in a chat or websocket server, but slower than a multithreaded, blocking approach when you have small numbers of active connections. What you really want is a system that supports both models, so you can choose the best one for your problem.
Some Node developers, for example the ones behind Meteor, have come to the conclusion that callback/promise-driven development is painful and tedious, and started wrapping up the asynchronous behavior in coroutines.
Unfortunately, to participate in this sort of world, where you wrap the asynchronous spaghetti into what the "Node.js Is Badass Rockstar Tech" video would call "sequential code, you know, the code you can read", each async library must be hand-plummed into a synchronous version that does the coroutine juggling.
This puts Node in exactly the situation it was trying to escape: now there are two different I/O models, one synchronous, one asynchronous, and not all libraries support the synchronous model, so by trying to leverage it, you're cutting yourself out of large parts of the Node ecosystem.
Better environments abstract over the I/O scheduling, letting you choose between threaded, blocking I/O, and a M:N task scheduler which can handle many lightweight tasks being scheduled on native threads. Rust comes to mind.
It was about how essentially, in a large concurrent system, a chain of callbacks like:
cb1 -> cb2 -> cb3|eb3 then cb3->cb4 and eb3->cb5
(select/epoll returns and calls cb1, chain ends with cb4 or cb5 depending if errback eb3 is called).
Is just a messy, dangerous and confusing re-implementation of threads/goroutines/tasks. Besides spreading the business logic among multiple io related function or sprinkling yields() or thens() it doesn't completely save one from needing locks and semaphores if shared or non-local data is modified.
A second callback chain from cb1 could have started before the previous one finished. Now they both could be modifying the same data. Yes granularity level in this is at code block level between IO points not assembly instruction, as the system grows large this problems becomes apparent.
I had to deal with it in a Python Twisted based framework. There is Twisted Semaphore and I had to use it.
The node.js and reactor-based event loops look _very_ nice in small demos and when the callback chain is shallow. HAproxy or nginx are good examples of this. They have shallow callback chain. Node.js demo example also look good, "Oh look you can serve 'Hello World' in 5 lines on a websocket!'" stuff like that.
As systems grow larger, callbacks chains as concurrency mechanisms start to suck.
It's all async under the hood because, in the end, you can only really resolve one thing at a time. Just because generators and promises let you write a more synchronous style of programming, essentially sugar, it doesn't mean that it's actually working in a true sync fashion.
When people start describing how Node works rather than bloviating blind praise, the first thought I have is, Windows. Yeah thats how windows works too. Im totally sold now!
Comments
First: reactor-based event loops aren't always a performance optimization. Round tripping I/O operations through an event loop adds latency. Node's approach is great if you have large numbers of mostly idle connections, such as in a chat or websocket server, but slower than a multithreaded, blocking approach when you have small numbers of active connections. What you really want is a system that supports both models, so you can choose the best one for your problem.
Some Node developers, for example the ones behind Meteor, have come to the conclusion that callback/promise-driven development is painful and tedious, and started wrapping up the asynchronous behavior in coroutines.
Unfortunately, to participate in this sort of world, where you wrap the asynchronous spaghetti into what the "Node.js Is Badass Rockstar Tech" video would call "sequential code, you know, the code you can read", each async library must be hand-plummed into a synchronous version that does the coroutine juggling.
This puts Node in exactly the situation it was trying to escape: now there are two different I/O models, one synchronous, one asynchronous, and not all libraries support the synchronous model, so by trying to leverage it, you're cutting yourself out of large parts of the Node ecosystem.
Better environments abstract over the I/O scheduling, letting you choose between threaded, blocking I/O, and a M:N task scheduler which can handle many lightweight tasks being scheduled on native threads. Rust comes to mind.
I wrote a post on this in a Go topic here not too long ago:
https://news.ycombinator.com/item?id=7388790
It was about how essentially, in a large concurrent system, a chain of callbacks like:
cb1 -> cb2 -> cb3|eb3 then cb3->cb4 and eb3->cb5
(select/epoll returns and calls cb1, chain ends with cb4 or cb5 depending if errback eb3 is called).
Is just a messy, dangerous and confusing re-implementation of threads/goroutines/tasks. Besides spreading the business logic among multiple io related function or sprinkling yields() or thens() it doesn't completely save one from needing locks and semaphores if shared or non-local data is modified.
A second callback chain from cb1 could have started before the previous one finished. Now they both could be modifying the same data. Yes granularity level in this is at code block level between IO points not assembly instruction, as the system grows large this problems becomes apparent.
I had to deal with it in a Python Twisted based framework. There is Twisted Semaphore and I had to use it.
The node.js and reactor-based event loops look _very_ nice in small demos and when the callback chain is shallow. HAproxy or nginx are good examples of this. They have shallow callback chain. Node.js demo example also look good, "Oh look you can serve 'Hello World' in 5 lines on a websocket!'" stuff like that.
As systems grow larger, callbacks chains as concurrency mechanisms start to suck.
It's all async under the hood because, in the end, you can only really resolve one thing at a time. Just because generators and promises let you write a more synchronous style of programming, essentially sugar, it doesn't mean that it's actually working in a true sync fashion.
When people start describing how Node works rather than bloviating blind praise, the first thought I have is, Windows. Yeah thats how windows works too. Im totally sold now!