libuv, NodeJS and everything in between

38
LIBUV, NODEJS AND EVERYTHING IN BETWEEN Saúl Ibarra Corretgé @saghul

Transcript of libuv, NodeJS and everything in between

Page 1: libuv, NodeJS and everything in between

LIBUV, NODEJS AND EVERYTHING IN BETWEENSaúl Ibarra Corretgé @saghul

Page 2: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

HIYA!

▸ @saghul

▸ Bilbao — Amsterdam — Liverpool

▸ libuv core janitor

▸ Nodejs collaborator

▸ Recently obsessed with 3D printing

▸ AMA!

Page 3: libuv, NodeJS and everything in between

ASYNC I/O 101

Page 4: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

ASYNC I/O 101

from __future__ import print_function

import socket

server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) server.bind(('127.0.0.1', 1234)) server.listen(10) print("Server listening on: {}".format(server.getsockname()))

client, addr = server.accept() print("Client connected: {}".format(addr))

while True: data = client.recv(4096) if not data: print("Client has disconnected") break client.send(data.upper())

server.close()

Page 5: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

CRAP, IT BLOCKS!

▸ Only one client at a time!

▸ Scaling up

▸ Threads

▸ I/O multiplexing

▸ http://www.kegel.com/c10k.html

▸ http://www.slideshare.net/saghul/how-do-eventloops-work-in-python

Page 6: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

THREADS?

▸ Overhead: stack size, scheduling

▸ Synchronisation

▸ It’s still a good solution in some cases

Page 7: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

I/O MULTIPLEXING TO THE RESCUE

▸ Single thread

▸ Examine and wait for i/o in multiple sockets at once

▸ When a socket is ready the operation won’t block

▸ Good for i/o bound tasks, bad for CPU intensive tasks

Page 8: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

I/O MULTIPLEXING 101

1. Put the fd in non-blocking mode

2. Add the fd to the i/o multiplexor

3. Wait for some time

4. Perform read / writes on the fd, it won’t block!

5. Profit?

Page 9: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

I/O MULTIPLEXING APIS

▸ Different APIs: select, poll, epoll, kqueue, …

▸ Unix shares a common model: readiness

▸ Windows uses a completion model

▸ IOCP is The Good Stuff

Page 10: libuv, NodeJS and everything in between

LIBUV

Page 11: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV

▸ Small (relatively) C library: ~30K LOC (without tests)

▸ Extensive test suite

▸ Designed for C programs that miss the joy of JavaScript callback hell

▸ Used by Node and many other projects:https://github.com/libuv/libuv/wiki/Projects-that-use-libuv

Page 12: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: FEATURES

▸ Event loop backed by epoll, kqueue, IOCP, event ports, etc.

▸ Timers

▸ Async TCP / UDP sockets

▸ Async named pipes

▸ Async filesystem operations

▸ Async signal handling

▸ Child processes

▸ ANSI escaped TTY

▸ Threading utilities

▸ Coolest logo ever

Page 13: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: ARCHITECTURE

Page 14: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: THE EVENT LOOP

Page 15: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: FILESYSTEM APIS

▸ Follow the Unix style

▸ Executed in a thread pool

▸ http://blog.libtorrent.org/2012/10/asynchronous-disk-io/

Page 16: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: A WORD ON THREADS

▸ We only use threads for file i/o and getaddrinfo

▸ Default thread pool size is 4(runtime env var: UV_THREADPOOL_SIZE)

▸ NOT FOR NETWORK I/O

▸ NOT FOR NETWORK I/O

▸ NOT FOR NETWORK I/O

▸ NOT FOR NETWORK I/O

Page 17: libuv, NodeJS and everything in between

THE LIBUV EVENT LOOP IS SINGLE THREADED. THE THREAD POOL IS ONLY USED FOR FILE I/O.

The libuv police

LIBUV, NODE AND EVERYTHING IN BETWEEN

Page 18: libuv, NodeJS and everything in between

A CHAT APP, OF COURSE

Page 19: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

A LIBUV CHAT APPLICATION

▸ Simple TCP server application

▸ A twist on libuv-chat by Ben Noordhuis in 2011

▸ Multiple users, single “chat room”

▸ Pokemon, because why not?

▸ https://github.com/saghul/libuv-chat

Page 20: libuv, NodeJS and everything in between

THE MAKEFILE

all: build

deps/libuv: git clone --depth 1 https://github.com/libuv/libuv deps/libuv

build/gyp: git clone --depth 1 https://chromium.googlesource.com/external/gyp build/gyp

out/Makefile: deps/libuv build/gyp build/gyp/gyp -Duv_library=static_library --depth=$(PWD) --generator-output=$(PWD)/out -Goutput_dir=$(PWD)/out -f make build.gyp

build: out/Makefile $(MAKE) -C out

clean: rm -rf out

distclean: rm -rf build deps out

.PHONY: clean distclean

Page 21: libuv, NodeJS and everything in between

THE GYP BUILD FILE

# Copyright (c) 2016, Saúl Ibarra Corretgé <[email protected]> # Copyright (c) 2012, Ben Noordhuis <[email protected]> # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# gyp -Duv_library=static_library --depth=$PWD --generator-output=$PWD/out -Goutput_dir=$PWD/out -f make build.gyp

{ 'targets': [ { 'target_name': 'chat-server', 'type': 'executable', 'dependencies': ['deps/libuv/uv.gyp:libuv'], 'sources': ['src/main.c', 'src/queue.h', 'src/pokemon_names.h'], } ] }

Page 22: libuv, NodeJS and everything in between

INITIALISE AND RUN SERVERstruct user { QUEUE queue; uv_tcp_t handle; char id[32]; };

static QUEUE users;

int main(void) { QUEUE_INIT(&users);

srand(1234);

int r;

uv_tcp_t server_handle; uv_tcp_init(uv_default_loop(), &server_handle);

struct sockaddr_in addr; uv_ip4_addr(SERVER_ADDR, SERVER_PORT, &addr);

r = uv_tcp_bind(&server_handle, (const struct sockaddr*) &addr, 0); if (r < 0) fatal("uv_tcp_bind", r);

const int backlog = 128; r = uv_listen((uv_stream_t*) &server_handle, backlog, on_connection); if (r < 0) fatal("uv_listen", r);

printf("Listening at %s:%d\n", SERVER_ADDR, SERVER_PORT); uv_run(uv_default_loop(), UV_RUN_DEFAULT);

return 0; }

Page 23: libuv, NodeJS and everything in between

HANDLE INCOMING CONNECTIONS

static void on_connection(uv_stream_t* server_handle, int status) { assert(status == 0); int r;

// hurray, a new user! struct user *user = xmalloc(sizeof(*user)); uv_tcp_init(uv_default_loop(), &user->handle);

r = uv_accept(server_handle, (uv_stream_t*) &user->handle); if (r < 0) fatal("uv_accept", r);

// add him to the list of users QUEUE_INSERT_TAIL(&users, &user->queue); make_user_id(user);

// now tell everyone, including yourself (to know your name!) broadcast(NULL, "* A wild %s appeared from %s\n", user->id, addr_and_port(user));

// start accepting messages from the user uv_read_start((uv_stream_t*) &user->handle, on_alloc, on_read); }

Page 24: libuv, NodeJS and everything in between

READ INCOMING DATA

static void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { buf->base = xmalloc(suggested_size); buf->len = suggested_size; }

static void on_read(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) { struct user *user = QUEUE_DATA(handle, struct user, handle);

if (nread == UV_EOF) { // user disconnected QUEUE_REMOVE(&user->queue); uv_close((uv_handle_t*) &user->handle, on_close); broadcast(NULL, "* %s fled!\n", user->id); } else if (nread > 0) { // broadcast message broadcast(user, "%s said: %.*s", user->id, (int) nread, buf->base); } else { fprintf(stderr, "on_read: %s\n", uv_strerror(nread)); }

free(buf->base); }

Page 25: libuv, NodeJS and everything in between

BROADCAST DATA

static void broadcast(const struct user* sender, const char *fmt, ...) { QUEUE *q; char msg[512]; va_list ap;

va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap);

QUEUE_FOREACH(q, &users) { struct user *user = QUEUE_DATA(q, struct user, queue); if (user != sender) { unicast(user, msg); } } }

static void unicast(struct user *user, const char *msg) { size_t len = strlen(msg); uv_write_t *req = xmalloc(sizeof(*req) + len); void *addr = req + 1; memcpy(addr, msg, len); uv_buf_t buf = uv_buf_init(addr, len); uv_write(req, (uv_stream_t*) &user->handle, &buf, 1, on_write); }

static void on_write(uv_write_t *req, int status) { free(req); }

Page 26: libuv, NodeJS and everything in between

MISCELLANEOUS FUNCTIONS

static void on_close(uv_handle_t* handle) { struct user *user = QUEUE_DATA(handle, struct user, handle); free(user); }

static void make_user_id(struct user *user) { snprintf(user->id, sizeof(user->id), "%s", pokemon_names[rand() % ARRAY_SIZE(pokemon_names)]); }

Page 27: libuv, NodeJS and everything in between

NODE

Page 28: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

NODEJS

▸ Server side JavaScript

▸ V8 JavaScript engine

▸ Web inspired async model

▸ Auxiliary libraries: libuv, OpenSSL, c-ares, http_parser, …

Page 29: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

PLATFORM LAYER REQUIREMENTS

▸ ASYNC ALL THE THINGS

▸ Completion based (callbacks)

▸ Consistent API for asynchronous network i/o

▸ Filesystem operations

▸ Basic name resolution (getaddrinfo / getnameinfo)

Page 30: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

ARCHITECTURE IN NODE 0.4

BINDINGS / WRAPS

JS STANDARD LIBRARY

USER APPLICATIONS

V8 OPENSSL LIBEV LIBEIO …

Page 31: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

ARCHITECTURE IN NODE 0.6

BINDINGS / WRAPS

JS STANDARD LIBRARY

USER APPLICATIONS

V8 OPENSSL LIBUV …LIBEV LIBEIO

Page 32: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

ARCHITECTURE IN NODE 0.10 AND ONWARDS

BINDINGS / WRAPS

JS STANDARD LIBRARY

USER APPLICATIONS

V8 OPENSSL LIBUV …

Page 33: libuv, NodeJS and everything in between

LIBUV IN NODE

Page 34: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

LIBUV: THE EVENT LOOPREMEMBER?

Page 35: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

NODE EVENT LOOPCOALESCED, 1 NODE TIMER != 1 LIBUV

TIMER

RUN ON A CHECK + IDLE

HANDLENEXT TICK CALLBACKS RUN FROM NODE::MAKECALLBACK

Page 36: libuv, NodeJS and everything in between

LIBUV, NODE AND EVERYTHING IN BETWEEN

ONION ARCHITECTURE (TM)net.Socket

TCPWrap

uv_tcp_t

Socket._handle

TCPWrap.handle_

fd / HANDLE

Page 37: libuv, NodeJS and everything in between

QUESTIONS?

libuv.org docs.libuv.org #libuv on IRC

libuv Google Group #libuv on StackOverflow

Page 38: libuv, NodeJS and everything in between

QUESTIONS?

@saghul bettercallsaghul.com