THIS IS NO LONGER UP TO DATE, PPT/PDF IS LATEST
Sam Roberts
github: @sam-github
email: [email protected]
twitter: @octetcloud
- What is the event loop? (Hint: its not an EventEmitter)
- When is node multi-threaded?
- Why is Node.js said to "scale well"?
Warning: Pseudo "C" code lies ahead!
Network connections use "sockets", named after the system call used:
int s = socket();
Sockets are referred to (confusingly) as "file descriptors", these are not necessarily references to the file system. Sorry.
File descriptors are O/S "object orientation", they point to objects in the kernel with a virtual "interface" (read/write/close/etc.).
int server = socket();
bind(server, 80)
listen(server)
while(int connection = accept(server)) {
pthread_create(echo, connection)
}
void echo(int connection) {
char buf[4096];
while(int size = read(connection, buffer, sizeof buf)) {
write(connection, buffer, size);
}
int server = ... // like before
int eventfd = epoll_create1(0);
struct epoll_event events[10];
struct epoll_event ev = { .events = EPOLLIN, .data.fd = server };
epoll_ctl(epollfd, EPOLL_CTL_ADD, server, &ev);
// This *is* the "event loop", every pass is a "tick"
while((int max = epoll_wait(eventfd, events, 10, -1))) {
for(n = 0; n < max; n++) {
if (events[n].data.fd.fd == server) {
// Server socket has connection!
int connection = accept(server);
ev.events = EPOLLIN; ev.data.fd = connection;
epoll_ctl(eventfd, EPOLL_CTL_ADD, connection, &ev);
} else {
// Connection socket has data!
char buf[4096];
int size = read(connection, buffer, sizeof buf);
write(connection, buffer, size);
}
}}
A semi-infinite loop, polling and blocking on the O/S until some in a set of file descriptors are ready.
It exits when it no longer has an events to
epoll_wait()
for, so will never have any more events to process. At that point the epoll loop must complete.
Note: .unref()
marks handles that are being waited on in the loop as "not
counting" towards keeping node alive.
Yes and no.
- "file" descriptors: yes, but not actual disk files (sorry)
- time: yes
- anything else... indirectly
Classic, well supported.
poll(..., int timeout)
kqueue(..., struct timespec* timeout)
epoll_wait(..., int timeout, ...)
timeout
resolution is milliseconds, timespec
is nanoseconds, but rounded up
to system clock granularity.
Only one timeout at a time, but Node.js keeps all timeouts sorted, and sets the timeout value to the next/earliest timeout.
fs.*
use the uv
thread pool (unless they are sync).
The blocking call is made by a thread, and when it completes, readiness is signalled back to epoll loop using either an eventfd or a self-pipe.
A pipe, where one end is written to by a thread or signal handler, and the other end is polled in the epoll loop.
Traditional way to "wake up" a polling loop when the event to wait for is not directly representable as a file descriptor.
dns.lookup()
callsgetaddrinfo()
, a function in the system resolver library that makes blocking socket calls and cannot be integrated into a polling loop.dns.<everything else>
uses non-blocking I/O, and integrates with the epoll loop
Docs bend over backwards to explain this, but once you know how the event loop works, and how blocking library calls must be shunted off to the thead pool, this will always makes sense.
It is shared by:
fs
,dns
,http.request()
(with a name,dns.lookup()
is used to resolve), and- any C++ addons that use it.
Default number of threads is 4, significantly parallel users of the above should increase the size.
Hints:
- Resolve DNS names yourself, directly, using the direct APIs to avoid
dns.lookup()
. - Increase the thread pool size with
UV_THREADPOOL_SIZE
.
The ultimate async... uses the self-pipe pattern to communicate with epoll loop.
Note that attaching callbacks for signals doesn't "ref" the event loop, which is consistent with their usage as a "probably won't happen" IPC mechanism.
- Unix signals child process termination with
SIGCHLD
- Pipes between the parent and child are pollable.
Addons should use the UV thread pool, but can do anything, including making blocking calls which will block the loop (perhaps unintentionally).
Hints:
- Review their code
- Track loop metrics
- What is the event loop
- When is node multi-threaded
- Why it "scales well"
This talk, including compilable version of pseudo "C" for playing with:
Bert Belder's talk about the Node.js event loop from a higher level, the "outside in":