diff --git a/.gitignore b/.gitignore index 1f43a2f..3d1ac77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +.vscode +.idea +.history + lib-cov *.seed *.log @@ -17,3 +21,5 @@ deps build out *.xcodeproj + +.DS_Store diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..a616492 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +2.7.18 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1a622ca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "files.associations": { + "*.gz": "json", + "*.sql": "sql", + ".*yml": "yaml", + "locale": "c", + "ios": "c", + "array": "c", + "string": "c", + "string_view": "c", + "vector": "c", + "learnuv.h": "c" + } +} diff --git a/README.md b/README.md index fdf96d7..c215498 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,90 @@ -# learnuv +# experiment-libuv -Learn uv for fun and profit, a self guided workshop to [the library](https://github.com/libuv/libuv) that powers Node.js. +This is a fork of [thlorenz/learnuv](https://github.com/thlorenz/learnuv), where I did my own exploration. -![screenshot](assets/screenshot.png) +## How it started -## Before You Start +It all started with this article series: [Event Loop and the Big Picture — NodeJS Event Loop Part 1](https://blog.insiderattack.net/event-loop-and-the-big-picture-nodejs-event-loop-part-1-1cb67a182810). -Read the [learnuv gitbook](http://thlorenz.github.io/learnuv/book) which explains some libuv and related C language -concepts that will help you complete the exercises. +The articles explain how the Node.js event loop works under the hood, and how it maps to libuv phases. I wanted to go deeper — not just read about it, but actually run libuv locally and see it in action. -## Installation +So I forked `learnuv` and worked through the C exercises to understand the raw event loop: timers, I/O polling, idle handlers, check handlers, and close handlers. -Requires [Python](https://www.python.org/downloads/) 2.6 or 2.7 to be installed. -If python is not in your path set the environment variable `PYTHON` to its -location. For example on Windows: `set PYTHON=C:\Python27\python.exe` +Once I had a feel for the libuv internals, I went back to Node.js and wrote [`nodejs/event_loop_sandbox/index.js`](nodejs/event_loop_sandbox/index.js) — a JavaScript sandbox that maps each libuv concept onto the corresponding Node.js APIs (`setTimeout`, `setImmediate`, `process.nextTick`, Promises, etc.). -```sh -git clone https://github.com/thlorenz/learnuv && cd learnuv -npm install -``` - -## Getting Started +## Repository structure -```sh -./learnuv help +``` +src/ # C exercises using libuv (forked from learnuv) + 01_system_info.c + 02_idle.c + 03_fs_readsync.c + 04_fs_readasync.c + 05_fs_readasync_context.c + 06_fs_allasync.c + 07_tcp_echo_server.c + 08_horse_race.c + event_loop_sandbox/ # libuv event loop diagrams + libuv-event-loop.png + node-event-loop.png + ... +nodejs/ + event_loop_sandbox/ + index.js # Node.js sandbox mapping libuv phases to JS APIs ``` -## Building - -**learnuv** comes with build commands that you can use instead of the manual steps explained further below. - -- **./learnuv ninja** - Compile your sources with ninja, stored inside `./out/Debug` -- **./learnuv make** - Compile your sources with make, stored inside `./out/Debug` -- **./learnuv xcode** - Generate a learnuv Xcode project, named `./learnuv.xcodeproj` - -### Windows Caveats +## Before You Start (C side) -Ninja and Make do not work on windows except via [cygwin](https://www.cygwin.com/). +1. C Introduction — https://www.w3schools.com/c/c_intro.php +2. Pointers — https://www.w3schools.com/c/c_pointers.php +3. Address-Of / Dereference +4. Struct — https://www.w3schools.com/c/c_structs.php +5. Automatic Allocation vs Dynamic Allocation (malloc) +6. NULL +7. Typecast — https://www.tutorialspoint.com/cprogramming/c_type_casting.htm +8. LibUV guidebook (5 mins reading) — https://thlorenz.com/learnuv/book/ -### Ninja +And the official [LibUV documentation](https://docs.libuv.org/en/v1.x/guide/introduction.html). -Highly recommended since it builds faster than any of the other options, so [get ninja](https://ninja-build.org/) -and do: +## Setup for `macOS 14.5 23F79` ```sh -./gyp_learnuv.py -f ninja -ninja -C out/Debug -./out/Debug/01_system_info +npm install ``` -### Make +```sh +pyenv install 2.7 && +pyenv local 2.7.18 +``` -Works on Linux and OSX. +```sh +pip install six # https://github.com/thlorenz/learnuv/issues/45 +``` ```sh -./gyp_learnuv.py -f make -make -C out -./out/Debug/01_system_info +eval "$(pyenv init -)" # for some reason it's not working from ~/.zshrc ``` -### Xcode +```sh +./learnuv +``` -Works on OSX only. Highly recommended to ease debugging and code navigation. +```sh +./learnuv xcode +``` ```sh -./gyp_learnuv.py -f xcode open learnuv.xcodeproj ``` -### Visual Studio - -Works on Windows only. **TODO** need to adapt [vcbuild.bat](https://github.com/joyent/libuv/blob/master/vcbuild.bat). +```sh +./learnuv verify +``` -## Running +## Running the Node.js sandbox -``` -./learnuv +```sh +node nodejs/event_loop_sandbox ``` ## License diff --git a/assets/screenshot.png b/assets/screenshot.png deleted file mode 100644 index 5db2bff..0000000 Binary files a/assets/screenshot.png and /dev/null differ diff --git a/exercises/07_tcp_echo_server/exercise.js b/exercises/07_tcp_echo_server/exercise.js index 1dfc117..5dbb4b3 100644 --- a/exercises/07_tcp_echo_server/exercise.js +++ b/exercises/07_tcp_echo_server/exercise.js @@ -1,86 +1,98 @@ -'use strict'; -var net = require('net') - , colors = require('ansicolors') - , format = require('util').format - , table = require('text-table') +"use strict"; +var net = require("net"), + colors = require("ansicolors"), + format = require("util").format, + table = require("text-table"); -var HOST = '0.0.0.0' - , PORT = 7000; +var HOST = "0.0.0.0", + PORT = 7001; -var exercise = require('workshopper-exercise')() +var exercise = require("workshopper-exercise")(); exercise.requireSubmission = false; -exercise.addSetup(checkClientResponse); +exercise.addSetup(checkClientResponse); module.exports = exercise; function log() { - console.log(colors.green('progress ') + colors.brightBlack(format.apply(this, arguments))); + console.log( + colors.green("progress ") + + colors.brightBlack(format.apply(this, arguments)) + ); } function checkClientResponse(mode, cb) { var progress = { - connected : false - , responded : false - , correctResponse : false - , closed : false - , error : false - } + connected: false, + responded: false, + correctResponse: false, + closed: false, + error: false, + }; - var fail = false - , quit = false - , finished = false - , msg = 'Hello Server can you echo?' - , timeout + var fail = false, + quit = false, + finished = false, + msg = "Hello Server can you echo?", + timeout; function finish() { clearTimeout(timeout); if (finished) return; finished = true; - var arr = Object.keys(progress) - .reduce(function key(acc, k) { - if (k === 'error') { fail = fail || !!progress[k]; return acc } - fail = fail || !progress[k] - var label = colors.blue(k[0].toUpperCase() + k.slice(1)); - acc.push([ label, progress[k] ? colors.green('OK') : colors.red('NOT OK') ]) - return acc - }, []) + var arr = Object.keys(progress).reduce(function key(acc, k) { + if (k === "error") { + fail = fail || !!progress[k]; + return acc; + } + fail = fail || !progress[k]; + var label = colors.blue(k[0].toUpperCase() + k.slice(1)); + acc.push([ + label, + progress[k] ? colors.green("OK") : colors.red("NOT OK"), + ]); + return acc; + }, []); + + console.log("\n" + table(arr)); - console.log('\n' + table(arr)); - - if (progress.error) console.error('\nEncountered an error', progress.error); - cb(fail && mode === 'verify' ? new Error('Sorry something was not quite right yet :(') : null); + if (progress.error) console.error("\nEncountered an error", progress.error); + cb( + fail && mode === "verify" + ? new Error("Sorry something was not quite right yet :(") + : null + ); } // give up after 3s, i.e. if server is irresponsive or doesn't quit timeout = setTimeout(finish, 3000); var client = new net.Socket(); - log('Client connecting to server'); + log("Client connecting to server"); client .connect(PORT, HOST, function onconnection() { progress.connected = true; log('Client sending "%s"', msg); client.write(msg); - }) - .on('error', function onerror(err) { + }) + .on("error", function onerror(err) { progress.error = err; finish(); }) - .on('data', function ondata(data) { - progress.responded = true + .on("data", function ondata(data) { + progress.responded = true; var res = data.toString(); log('Server responded with "%s"', msg); - progress.correctResponse = (res === msg) + progress.correctResponse = res === msg; - log('Telling server to quit'); - client.write('QUIT'); + log("Telling server to quit"); + client.write("QUIT"); }) - .on('close', function onclose() { + .on("close", function onclose() { progress.closed = true; - log('Server closed connection') - finish() - }) + log("Server closed connection"); + finish(); + }); } diff --git a/exercises/07_tcp_echo_server/problem.md b/exercises/07_tcp_echo_server/problem.md index 7b46cd6..04e09cb 100644 --- a/exercises/07_tcp_echo_server/problem.md +++ b/exercises/07_tcp_echo_server/problem.md @@ -13,7 +13,7 @@ So lets start with the easy part. ### Starting the Server and Listening on a Port -First of all we need to initialize our server `tcp_server`. +First of all we need to initialize our server `tcp_server`. This is done via [`uv_tcp_init`](http://docs.libuv.org/en/latest/tcp.html#c.uv_tcp_init). Our server represents the TCP `handle`. @@ -57,8 +57,8 @@ you to carefully read through the code and understand the concepts and technique ## Hint -Use `nc localhost 7000` to test your server (finish via `Ctrl-D`) and/or stop the server by sending `QUIT`. -You can also send entire files, i.e. `cat package.json | netcat localhost 7000`. +Use `nc localhost 7001` to test your server (finish via `Ctrl-D`) and/or stop the server by sending `QUIT`. +You can also send entire files, i.e. `cat package.json | netcat localhost 7001`. On windows you may have to use telnet or putty. diff --git a/exercises/07_tcp_echo_server/solution.c b/exercises/07_tcp_echo_server/solution.c index 0a522a5..4b1f96b 100644 --- a/exercises/07_tcp_echo_server/solution.c +++ b/exercises/07_tcp_echo_server/solution.c @@ -1,37 +1,41 @@ #include "learnuv.h" #include -const static char* HOST = "0.0.0.0"; /* localhost */ -const static int PORT = 7000; -const static int NBUFS = 1; /* number of buffers we write at once */ +const static char *HOST = "0.0.0.0"; /* localhost */ +const static int PORT = 7001; +const static int NBUFS = 1; /* number of buffers we write at once */ static uv_tcp_t tcp_server; -typedef struct { +typedef struct +{ uv_write_t req; uv_buf_t buf; } write_req_t; /* forward declarations */ -static void close_cb(uv_handle_t* client); -static void server_close_cb(uv_handle_t*); -static void shutdown_cb(uv_shutdown_t*, int); +static void close_cb(uv_handle_t *client); +static void server_close_cb(uv_handle_t *); +static void shutdown_cb(uv_shutdown_t *, int); -static void alloc_cb(uv_handle_t*, size_t, uv_buf_t*); -static void read_cb(uv_stream_t*, ssize_t, const uv_buf_t*); -static void write_cb(uv_write_t*, int); +static void alloc_cb(uv_handle_t *, size_t, uv_buf_t *); +static void read_cb(uv_stream_t *, ssize_t, const uv_buf_t *); +static void write_cb(uv_write_t *, int); -static void close_cb(uv_handle_t* client) { +static void close_cb(uv_handle_t *client) +{ free(client); log_info("Closed connection"); } -static void shutdown_cb(uv_shutdown_t* req, int status) { - uv_close((uv_handle_t*) req->handle, close_cb); +static void shutdown_cb(uv_shutdown_t *req, int status) +{ + uv_close((uv_handle_t *)req->handle, close_cb); free(req); } -static void onconnection(uv_stream_t *server, int status) { +static void onconnection(uv_stream_t *server, int status) +{ CHECK(status, "onconnection"); int r = 0; @@ -46,34 +50,40 @@ static void onconnection(uv_stream_t *server, int status) { CHECK(r, "uv_tcp_init"); /* 4.2. Accept the now initialized client connection */ - r = uv_accept(server, (uv_stream_t*) client); - if (r) { + r = uv_accept(server, (uv_stream_t *)client); + if (r) + { log_error("trying to accept connection %d", r); shutdown_req = malloc(sizeof(uv_shutdown_t)); - r = uv_shutdown(shutdown_req, (uv_stream_t*) client, shutdown_cb); + r = uv_shutdown(shutdown_req, (uv_stream_t *)client, shutdown_cb); CHECK(r, "uv_shutdown"); } /* 5. Start reading data from client */ - r = uv_read_start((uv_stream_t*) client, alloc_cb, read_cb); + r = uv_read_start((uv_stream_t *)client, alloc_cb, read_cb); CHECK(r, "uv_read_start"); } -static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) { +static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) +{ /* libuv suggests a buffer size but leaves it up to us to create one of any size we see fit */ buf->base = malloc(size); buf->len = size; - if (buf->base == NULL) log_error("alloc_cb buffer didn't properly initialize"); + if (buf->base == NULL) + log_error("alloc_cb buffer didn't properly initialize"); } -static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { +static void read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) +{ int r = 0; uv_shutdown_t *shutdown_req; /* Errors or EOF */ - if (nread < 0) { - if (nread != UV_EOF) CHECK(nread, "read_cb"); + if (nread < 0) + { + if (nread != UV_EOF) + CHECK(nread, "read_cb"); /* Client signaled that all data has been sent, so we can close the connection and are done */ free(buf->base); @@ -84,19 +94,21 @@ static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { return; } - if (nread == 0) { + if (nread == 0) + { /* Everything OK, but nothing read and thus we don't write anything */ free(buf->base); return; } /* Check if we should quit the server which the client signals by sending "QUIT" */ - if (!strncmp("QUIT", buf->base, fmin(nread, 4))) { + if (!strncmp("QUIT", buf->base, fmin(nread, 4))) + { log_info("Closing the server"); free(buf->base); /* Before exiting we need to properly close the server via uv_close */ /* We can do this synchronously */ - uv_close((uv_handle_t*) &tcp_server, NULL); + uv_close((uv_handle_t *)&tcp_server, NULL); log_info("Closed server, exiting"); exit(0); } @@ -109,19 +121,21 @@ static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { CHECK(r, "uv_write"); } -static void write_cb(uv_write_t *req, int status) { +static void write_cb(uv_write_t *req, int status) +{ CHECK(status, "write_cb"); log_info("Replied to client"); /* Since the req is the first field inside the wrapper write_req, we can just cast to it */ /* Basically we are telling C to include a bit more data starting at the same memory location, which in this case is our buf */ - write_req_t *write_req = (write_req_t*) req; + write_req_t *write_req = (write_req_t *)req; free(write_req->buf.base); free(write_req); } -int main() { +int main() +{ int r = 0; uv_loop_t *loop = uv_default_loop(); @@ -129,17 +143,17 @@ int main() { r = uv_tcp_init(loop, &tcp_server); CHECK(r, "uv_tcp_init"); - /* 2. Bind to localhost:7000 */ + /* 2. Bind to localhost:7001 */ struct sockaddr_in addr; r = uv_ip4_addr(HOST, PORT, &addr); CHECK(r, "uv_ip4_addr"); - r = uv_tcp_bind(&tcp_server, (struct sockaddr*) &addr, AF_INET); + r = uv_tcp_bind(&tcp_server, (struct sockaddr *)&addr, AF_INET); CHECK(r, "uv_tcp_bind"); /* 3. Start listening */ /* uv_tcp_t inherits uv_stream_t so casting is ok */ - r = uv_listen((uv_stream_t*) &tcp_server, SOMAXCONN, onconnection); + r = uv_listen((uv_stream_t *)&tcp_server, SOMAXCONN, onconnection); CHECK(r, "uv_listen"); log_info("Listening on %s:%d", HOST, PORT); diff --git a/exercises/interactive_horse_race/problem.md b/exercises/interactive_horse_race/problem.md index c002fb8..22885b4 100644 --- a/exercises/interactive_horse_race/problem.md +++ b/exercises/interactive_horse_race/problem.md @@ -1,10 +1,10 @@ -# interactive horse race +# interactive horse race This is the one exercise where all you have to do is **have fun**! You earned it! ## Launching the horse race -Start the `interactive_horse_race` and then connect **5** players via `nc localhost 7000`. +Start the `interactive_horse_race` and then connect **5** players via `nc localhost 7001`. Once enough players are connected, the race begins. ## The rules @@ -18,7 +18,7 @@ Whoever reaches the end of the track first wins. This is the exercise where a lot of things we learned before come together to create this silly little game. One thing to notice is that now **we don't use threads for our horses** since really we aren't calculating much to move -them. +them. Instead we use `idle` callbacks (remember the second exercise) in order to update their position. This of course means we are doing so on the main thread and can draw the horse without dispatching. @@ -59,7 +59,7 @@ The very keen of you may have detected some memory leaks in our game. You are pe I challenge you to fix them using [valgrind](http://valgrind.org/) to detect them. I'll be honest with you though. Properly cleaning up what you allocate can be quite challenging especially in async code -we use with libuv. +we use with libuv. We have seen how to do this properly for smaller examples, however in this case I just did not have the time to do this properly for our horse race. diff --git a/learnuv.gyp b/learnuv.gyp index 5eb7a83..b12b352 100644 --- a/learnuv.gyp +++ b/learnuv.gyp @@ -27,6 +27,8 @@ ] }, 'targets': [ + { 'target_name': 'epam_workshop' , 'sources': [ './src/epam_workshop.c' ] } , + { 'target_name': 'libuv_sandbox' , 'sources': [ './src/libuv_sandbox.c' ] } , { 'target_name': '01_system_info' , 'sources': [ './src/01_system_info.c' ] } , { 'target_name': '02_idle' , 'sources': [ './src/02_idle.c' ] } , { 'target_name': '03_fs_readsync' , 'sources': [ './src/03_fs_readsync.c' ] } , diff --git a/nodejs/event_loop_sandbox/index.js b/nodejs/event_loop_sandbox/index.js new file mode 100644 index 0000000..4c83d6f --- /dev/null +++ b/nodejs/event_loop_sandbox/index.js @@ -0,0 +1,367 @@ +/** + * @fileoverview Event loop sandbox, try to understand how libuv works from POINT OF VIEW of JavaScript. + * Inspired by this article circle + * @link Event Loop and the Big Picture (This article) - https://blog.insiderattack.net/event-loop-and-the-big-picture-nodejs-event-loop-part-1-1cb67a182810 + * @link Timers, Immediates and Next Ticks - https://blog.insiderattack.net/timers-immediates-and-process-nexttick-nodejs-event-loop-part-2-2c53fd511bb3 + * @link Promises, Next-Ticks, and Immediates - https://blog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa + * @link Handling I/O - https://blog.insiderattack.net/handling-io-nodejs-event-loop-part-4-418062f917d1 + * @link Event Loop Best Practices - https://blog.insiderattack.net/event-loop-best-practices-nodejs-event-loop-part-5-e29b2b50bfe2 + * @link New changes to timers and microtasks in Node v11 - https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3 + * @link JavaScript Event Loop vs Node JS Event Loop - https://blog.insiderattack.net/javascript-event-loop-vs-node-js-event-loop-aea2b1b85f5c + +node nodejs/event_loop_sandbox + + */ + +console.log("Start of Script"); + +/** + * demultiplexer (libuv, libevent for Chrome) + * - receives I/O requests and delegates these requests to the appropriate hardware. + * - add the registered callback handler for the particular action (events) in a queue (Event Queue) to be processed + * - Reactor Pattern + * - epoll on Linux, kqueue on BSD systems (macOS), event ports in Solaris, IOCP (Input Output Completion Port) in Windows, etc + * - thread pool is used to handle the I/O operations, in case it cannot be directly addressed by hardware asynchronous I/O utils + * - NodeJS has done its best to do most of the I/O using non-blocking and asynchronous hardware I/O, but for the I/O types which blocks or are complex to address, it uses the thread pool. + */ + +/** + * Process vs Threads + * The primary difference between a process and a thread is that + * different processes cannot share the same memory space (code, variables, etc) + * whereas different threads in the same process share the same memory space. + * Threads are lightweight whereas Processes are heavyweight. + */ + +/** + * Threadpool + * - used to perform all File I/O operations, getaddrinfo and getnameinfo calls during DNS operations, randomBytes, randomFill and pbkdf2 are also run on the libuv thread pool + * - thread pool is limited (default size is 4, can be increased up to 128) + */ + +/** + * Event Loop (libuv, libevent for Chrome) + * - After processing one phase and before moving to the next phase, event loop will process two intermediate queues until no items are remaining in the intermediate queues. + * - Tracking the reference counter of total items to be processed - once it reaches zero, the event loop exits. + * 1. Expired timers and intervals queue (min-heap) (setTimeout, setInterval) + * 2. IO Events Queue + * 3. Immediate Queue (setImmediate) + * 4. Close Handlers Queue + * 0. (before moving between queues) Next Ticks Queue, Other Microtasks Queue (resolved/reject promise callbacks) + * - Next Ticks and Microtasks Queue will run between each individual setTimeout and setImmediate callbacks to match the browser behavior + */ + +/** + * Libuv Event Loop Phases + * 1. Timers — Expired timer and interval callbacks scheduled by `setTimeout` and `setInterval` will be invoked. + * 2. Pending I/O callbacks — Pending Callbacks of any completed/errored I/O operation to be executed here. + * 3. Idle, prepare — Used internally by NodeJS. + * 4. Prepare Handlers — Perform some prep-work before polling for I/O. + * 5. I/O Poll — Optionally wait for any I/O to complete. + * 6. Check handlers — Perform some post-mortem work after polling for I/O. Usually, callbacks scheduled by setImmediate will be invoked here. + * 7. Close handlers — Execute close handlers of any closed I/O operations (closed socket connection etc.) + * + * @link https://www.youtube.com/watch?v=sGTRmPiXD4Y + +// Check whether there are any referenced handlers to be invoked, or any active operations pending +r = uv__loop_alive(loop); +if (!r) + uv__update_time(loop); + +while (r != 0 && loop->stop_flag == 0) { + // This will send a system call to get the current time and update the loop time (This is used to identify expired timers). + uv__update_time(loop); + + // Run all expired timers + uv__run_timers(loop); + + // Run all completed/errored I/O callbacks + ran_pending = uv__run_pending(loop); + + uv__run_idle(loop); + uv__run_prepare(loop); + + // timeout to determine how long it should block for I/O + timeout = 0; + if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) + timeout = uv_backend_timeout(loop); + + // Poll for I/O, epoll_wait on Linux, kqueue on BSD systems (macOS), event ports in Solaris, GetQueuedCompletionStatus in IOCP (Input Output Completion Port) in Windows, etc + uv__io_poll(loop, timeout); + + // Run all check handlers (setImmediate callbacks will run here) + uv__run_check(loop); + + // Run all close handlers + uv__run_closing_handles(loop); + + if (mode == UV_RUN_ONCE) { + uv__update_time(loop); + uv__run_timers(loop); + } + + r = uv__loop_alive(loop); + if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) + break; +} + + +// Event loop will keep spinning as long as uv__loop_alive function returns true. +static int uv__loop_alive(const uv_loop_t* loop) { + return uv__has_active_handles(loop) || + uv__has_active_reqs(loop) || + loop->closing_handles != NULL; +} + +static int uv__run_pending(uv_loop_t* loop) { + QUEUE* q; + QUEUE pq; + uv__io_t* w; + + if (QUEUE_EMPTY(&loop->pending_queue)) + return 0; + + QUEUE_MOVE(&loop->pending_queue, &pq); + + while (!QUEUE_EMPTY(&pq)) { + q = QUEUE_HEAD(&pq); + QUEUE_REMOVE(q); + QUEUE_INIT(q); + w = QUEUE_DATA(q, uv__io_t, pending_queue); + w->cb(loop, w, POLLOUT); + } + + return 1; +} + +int uv_backend_timeout(const uv_loop_t* loop) { + if (loop->stop_flag != 0) + return 0; + + if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop)) + return 0; + + if (!QUEUE_EMPTY(&loop->idle_handles)) + return 0; + + if (!QUEUE_EMPTY(&loop->pending_queue)) + return 0; + + if (loop->closing_handles) + return 0; + + return uv__next_timeout(loop); +} + +// The event loop will not be blocked if there are any pending tasks to be executed. If there are no pending tasks to be executed, it will only be blocked until the next timer goes off, which re-activates the loop. +int uv__next_timeout(const uv_loop_t* loop) { + const struct heap_node* heap_node; + const uv_timer_t* handle; + uint64_t diff; + + heap_node = heap_min((const struct heap*) &loop->timer_heap); + if (heap_node == NULL) + return -1; // block indefinitely + + handle = container_of(heap_node, uv_timer_t, heap_node); + if (handle->timeout <= loop->time) + return 0; + + diff = handle->timeout - loop->time; + if (diff > INT_MAX) + diff = INT_MAX; + + return diff; +} + */ + +/** + * Next tick queue is not natively provided by the libuv (four main phases), but implemented in Node. + */ +function nextTickQueue() { + setTimeout(() => console.log("timeout1")); + setTimeout(() => { + console.log("timeout2"); + Promise.resolve().then(() => console.log("promise resolve")); + }); + setTimeout(() => console.log("timeout3")); + setTimeout(() => console.log("timeout4")); +} +// nextTickQueue(); + +/** + * IO starvation + * - Extensively filling up the next tick queue using process.nextTick function will force the event loop to keep processing the next tick queue indefinitely without moving forward. + * TODO: Write a code snippet to demonstrate IO starvation + */ +function ioStarvation() { + function addNextTickRecurs(count) { + let self = this; + if (self.id === undefined) { + self.id = 0; + } + + if (self.id === count) return; + + process.nextTick(() => { + console.log(`process.nextTick call ${++self.id}`); + addNextTickRecurs.call(self, count); + }); + } + + addNextTickRecurs(10); + // Node add a timer to the timers heap (in memory) + setTimeout(console.log.bind(console, "omg! setTimeout was called"), 10); + setImmediate(console.log.bind(console, "omg! setImmediate also was called")); +} +// ioStarvation(); + +/** + * Node check against time expiration. + * Time is taken on each loop run + * @link https://docs.libuv.org/en/v1.x/design.html#the-i-o-loop | concept of 'now' + */ +function TimerExecution() { + const start = process.hrtime(); + + setTimeout(() => { + const end = process.hrtime(start); + console.log( + `timeout callback executed after ${end[0]}s and ${ + end[1] / Math.pow(10, 9) + }ms` + ); + }, 1000); +} +// TimerExecution(); + +/** + * As you might guess, this program will always print setTimeout before setImmediate + * because the expired timer queue are processed before immediate. + * !BUT + * The minimum timeout to 1ms in order to align with Chrome's timers cap + * @link https://chromium.googlesource.com/chromium/blink/+/master/Source/core/frame/DOMTimer.cpp#93 + * So sometimes, setImmediate will be executed before setTimeout. + */ +function TimeoutVsImmediate() { + setTimeout(function () { + console.log("setTimeout"); + }, 0); + setImmediate(function () { + console.log("setImmediate"); + }); +} +// TimeoutVsImmediate(); + +/** + * Next Ticks and Microtasks (resolved/reject promise) Queues (0) are processed before every phase of the event loop. + */ +function PromiseVsImmediate() { + new Promise(() => console.log("promise executor")); + Promise.resolve().then(() => console.log("promise1 resolved")); + Promise.resolve().then(() => console.log("promise2 resolved")); + Promise.resolve().then(() => { + console.log("promise3 resolved"); + process.nextTick(() => + console.log("next tick inside promise resolve handler") + ); + }); + Promise.resolve().then(() => console.log("promise4 resolved")); + Promise.resolve().then(() => console.log("promise5 resolved")); + /** + * Timeout (1) handled earlier than Immediate (3) + */ + setTimeout(() => console.log("setTimeout"), 0); + setImmediate(() => console.log("set immediate1")); + setImmediate(() => console.log("set immediate2")); +} +// PromiseVsImmediate(); + +/** + * It's the same example as PromiseVsImmediate + */ +function MicrotasksVsMacrotasks() { + queueMicrotask(() => console.log("queueMicrotask1 resolved")); + queueMicrotask(() => console.log("queueMicrotask2 resolved")); + setTimeout(() => { + console.log("set timeout3"); + queueMicrotask(() => console.log("inner queueMicrotask3 resolved")); + }, 0); + setTimeout(() => console.log("set timeout1"), 0); + setTimeout(() => console.log("set timeout2"), 0); + queueMicrotask(() => console.log("queueMicrotask4 resolved")); + queueMicrotask(() => { + console.log("queueMicrotask5 resolved"); + queueMicrotask(() => console.log("inner queueMicrotask6 resolved")); + }); + queueMicrotask(() => console.log("queueMicrotask7 resolved")); +} +// MicrotasksVsMacrotasks(); + +/** + * Even if you have set the timeout to 0, all NodeJS timers seem to have fired after at least1ms. + * Chrome seems to cap the minimum timeout to 1ms for the first 4 nested timers. But afterwards, the cap seems to be increased to 4ms. + * > "Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds." + * Both NodeJS and Chrome enforces a 1ms minimum timeout to all the timers + */ +function TimerRace() { + const startHrTime = () => { + if (typeof window !== "undefined") return performance.now(); + return process.hrtime(); + }; + + const getHrTimeDiff = (start) => { + if (typeof window !== "undefined") return performance.now() - start; + const [ts, tns] = process.hrtime(start); + return ts * 1e3 + tns / 1e6; + }; + + console.log("TimerRace starts"); + const start1 = startHrTime(); + const outerTimer = setTimeout(() => { + const start2 = startHrTime(); + console.log(`timer1: ${getHrTimeDiff(start1)}`); + setTimeout(() => { + const start3 = startHrTime(); + console.log(`timer2: ${getHrTimeDiff(start2)}`); + setTimeout(() => { + const start4 = startHrTime(); + console.log(`timer3: ${getHrTimeDiff(start3)}`); + setTimeout(() => { + const start5 = startHrTime(); + console.log(`timer4: ${getHrTimeDiff(start4)}`); + setTimeout(() => { + const start6 = startHrTime(); + console.log(`timer5: ${getHrTimeDiff(start5)}`); + setTimeout(() => { + const start7 = startHrTime(); + console.log(`timer6: ${getHrTimeDiff(start6)}`); + setTimeout(() => { + const start8 = startHrTime(); + console.log(`timer7: ${getHrTimeDiff(start7)}`); + setTimeout(() => { + console.log(`timer8: ${getHrTimeDiff(start8)}`); + }); + }); + }); + }); + }); + }); + }); + }); +} +TimerRace(); + +console.log("End of Script"); + +/** + * Best Practices + * - Avoid sync I/O inside repeatedly invoked code blocks + * - dns.lookup() vs dns.resolve*() + * > These functions are implemented quite differently than dns.lookup(). (dns.resolve does not overload the libuv threadpool) + * > They do not use getaddrinfo(3) and they always perform a DNS query on the network. This network communication is always done asynchronously, and does not use libuv's threadpool. + * - If there is performance drop consider (in terms of file I/O or crypto operations) + * - - Increasing the capacity of the threadpool up-to 128 threads by setting UV_THREADPOOL_SIZE environment variable. + * - - Resolve hostname to IP address using dns.resolve* function and use IP address directly + * - Event loop monitoring / https://github.com/mcollina/loopbench + */ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..31959bd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,408 @@ +{ + "name": "learnuv", + "version": "0.1.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "learnuv", + "version": "0.1.3", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "ansicolors": "~0.3.2", + "diff": "^5.2.0", + "text-table": "~0.2.0", + "workshopper": "~1.3.2", + "workshopper-exercise": "~1.1.0", + "workshopper-hooray": "~1.0.1", + "workshopper-more": "~1.0.1" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + }, + "node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + }, + "node_modules/cardinal": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.5.0.tgz", + "integrity": "sha512-HRGILv/FykuNVO7xCTiXMEPJxA7jMyHwEgljgcAZ3xkLSEhLvStqcTy6xamC1NH4Ja8hlc9VHlXKoSuFbI+7lA==", + "dependencies": { + "ansicolors": "~0.2.1", + "redeyed": "~0.5.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/cardinal/node_modules/ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha512-tOIuy1/SK/dr94ZA0ckDohKXNeBNqZ4us6PjMVLs5h1w2GBB6uPtOknp2+VF4F/zcy9LI70W+Z+pE2Soajky1w==" + }, + "node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ==", + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + }, + "node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colors-tmpl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/colors-tmpl/-/colors-tmpl-0.1.1.tgz", + "integrity": "sha512-MmreFWowwAJ/EOux+8M4mVLRL2TZTic4VCaLsA5o4HhujZzwz9jpMdhObqKzZyYO89I5VFZK2wTLfmB/e3ch6g==", + "deprecated": "no longer maintained", + "dependencies": { + "colors": "~0.6.2" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/esprima-fb": { + "version": "12001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-12001.1.0-dev-harmony-fb.tgz", + "integrity": "sha512-kj/HWGgQXRPKf7qAd8Woo2AaxRdT8kpTK2x1+c3zeucEYiIsGktmMC1N3q8J99R0ECMG7Ey/kywsR3qXPHBaXA==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figlet": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.7.0.tgz", + "integrity": "sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==", + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/map-async": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/map-async/-/map-async-0.1.1.tgz", + "integrity": "sha512-nim726/DRF1yuTrx3qNJcFNqJCgiFD98eh/49EdI5LWTspX4whkVvT0hOcMEVJWCijKkp519vCY1uD05Hklc6w==" + }, + "node_modules/marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==" + }, + "node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" + }, + "node_modules/msee": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/msee/-/msee-0.1.2.tgz", + "integrity": "sha512-x85F5MY0FjQpj8svpn9PZno6EpxgUIxP50Gs4kg57iyBbXO5IHavixsK2ZJgCUI4q6fNoFNdUxxEpabsucLZsQ==", + "dependencies": { + "cardinal": "^0.5.0", + "chalk": "~0.4.0", + "marked": "~0.3.0", + "nopt": "~2.1.1", + "xtend": "~2.1.1" + }, + "bin": { + "msee": "bin/msee" + } + }, + "node_modules/msee/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==" + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/redeyed": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.5.0.tgz", + "integrity": "sha512-AhuOInui6+UEtwpYhp+CqxpP6MD7tnEFTR6yNxEcqbodg+dYDBjDdJJoLsH3k2Wp3mTZkuigP2Bf1bwAd7Fd7Q==", + "dependencies": { + "esprima-fb": "~12001.1.0-dev-harmony-fb" + } + }, + "node_modules/resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w==", + "dependencies": { + "through": "~2.3.4" + } + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==", + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/terminal-menu": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/terminal-menu/-/terminal-menu-0.3.5.tgz", + "integrity": "sha512-djfyZudnVNR22xDiO6WtERJ4t0W+RVSGqh7XIsByKzSsdCa/QXwtOVnJaLZ8i6AsclshzG/o+Vb0EZQLLSj8uA==", + "dependencies": { + "charm": "~0.1.2", + "inherits": "~2.0.0", + "resumer": "~0.0.0", + "visualwidth": "~0.0.1" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==", + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "node_modules/through2/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/tuple-stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/tuple-stream/-/tuple-stream-0.0.2.tgz", + "integrity": "sha512-RLg676gaO9qAOLoamGLgSRgX2jU0IuyvTw5wIJGi3CowTJeSd5KFOZUmW5cuB3f7YSeuyiVKQ3v6bXby51YyuQ==", + "dependencies": { + "through": "~2.3.4" + } + }, + "node_modules/visualwidth": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/visualwidth/-/visualwidth-0.0.1.tgz", + "integrity": "sha512-mO7Gjg3o47WC+vMtBgNiupdhVnKYxMZXuZJ+ctT3bGPd1JlzWM4X7DGHN6OiYaT5RrcdJ1Ls/hasM4TzoHbZRw==" + }, + "node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/workshopper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/workshopper/-/workshopper-1.3.3.tgz", + "integrity": "sha512-s3rQN4ZzEljhGggkHdJXAtC06cJSjyL67MZ2Go1R+BtphIRGkySUMWcJaY5X6uK7OBlAWXwuz2Zuc8Qx53KI/A==", + "dependencies": { + "chalk": "~0.4.0", + "colors-tmpl": "~0.1.0", + "map-async": "~0.1.1", + "mkdirp": "~0.3.5", + "msee": "~0.1.1", + "optimist": "~0.6.1", + "terminal-menu": "~0.3.2", + "visualwidth": "0.0.1", + "xtend": "~3.0.0" + } + }, + "node_modules/workshopper-exercise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/workshopper-exercise/-/workshopper-exercise-1.1.0.tgz", + "integrity": "sha512-OqKWnw1o5aHNYgBhW5xah6bhHeD1YHVMCw3Jpr/SffILyrsNSJrTVbcaUikFZDkqrNzJCqkO/G6V3hUn32DHbw==", + "dependencies": { + "after": "~0.8.1", + "chalk": "~0.4.0", + "split": "~0.3.0", + "through2": "~0.4.1", + "tuple-stream": "0.0.2", + "xtend": "~2.1.2" + } + }, + "node_modules/workshopper-exercise/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/workshopper-hooray": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/workshopper-hooray/-/workshopper-hooray-1.0.1.tgz", + "integrity": "sha512-mXgy9ws2yr8QJDPI7leLHIKTC6NvFdZMdFK/cjbXsxMX3mayVOlTPAKcgw14MVq8Q9zEpTPORrQ+nikOaUMMcw==", + "dependencies": { + "colors": "^0.6.2", + "figlet": "^1.1.0" + } + }, + "node_modules/workshopper-more": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/workshopper-more/-/workshopper-more-1.0.1.tgz", + "integrity": "sha512-V7vDGUR1mb1VnxpygtOdoxAOVAi1JTIzvU7k/ypYf6nOs+ZBdx0EDbzfmfzVGTVvxhxi8nX7zMdOrOojiP43rQ==" + }, + "node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json index 49abfc8..291ca31 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,13 @@ "homepage": "https://github.com/thlorenz/learnuv", "dependencies": { "ansicolors": "~0.3.2", - "diff": "~1.0.8", + "diff": "^5.2.0", "text-table": "~0.2.0", "workshopper": "~1.3.2", "workshopper-exercise": "~1.1.0", "workshopper-hooray": "~1.0.1", "workshopper-more": "~1.0.1" }, - "devDependencies": {}, "keywords": [], "author": { "name": "Thorsten Lorenz", diff --git a/src/01_system_info.c b/src/01_system_info.c index 42df878..7a16b41 100644 --- a/src/01_system_info.c +++ b/src/01_system_info.c @@ -1,6 +1,8 @@ #include "learnuv.h" -int main() { +int main() +{ + log_info("01_system_info"); int err; double uptime; @@ -10,6 +12,11 @@ int main() { log_report("Uptime: %f", uptime); size_t resident_set_memory; + err = uv_resident_set_memory(&resident_set_memory); + CHECK(err, "uv_uptime"); + + log_info("RSS: %ld", resident_set_memory); + log_report("RSS: %ld", resident_set_memory); return 0; } diff --git a/src/02_idle.c b/src/02_idle.c index 85c1640..2fcdc5c 100644 --- a/src/02_idle.c +++ b/src/02_idle.c @@ -1,22 +1,41 @@ #include "learnuv.h" -void idle_cb(uv_idle_t* handle) { +void idle_cb(uv_idle_t *handle) +{ static int64_t count = -1; count++; - if ((count % 10000) == 0) log_report("."); - if (count >= 50000) uv_idle_stop(handle); + if ((count % 10000) == 0) + { + log_info("."); + log_report("."); + } + if (count >= 50000) + { + log_info("uv_idle_stop"); + uv_idle_stop(handle); + } } -int main() { - uv_idle_t idle_handle; +int main() +{ + log_info("running 02_idle"); + + uv_idle_t idle; /* 1. create the event loop */ + // TODO: Why * here? + // http://docs.libuv.org/en/latest/loop.html + uv_loop_t *loop = uv_default_loop(); - /* 2. initialize an idle handler for the loop */ + /* 2. init an idle handler for the loop */ + // http://docs.libuv.org/en/latest/idle.html + uv_idle_init(loop, &idle); /* 3. start the idle handler with a function to call */ + uv_idle_start(&idle, idle_cb); /* 4. start the event loop */ + uv_run(loop, UV_RUN_DEFAULT); return 0; } diff --git a/src/03_fs_readsync.c b/src/03_fs_readsync.c index 5b67780..482259d 100644 --- a/src/03_fs_readsync.c +++ b/src/03_fs_readsync.c @@ -3,35 +3,66 @@ #define BUF_SIZE 37 static const char *filename = __MAGIC_FILE__; -int main() { +int main() +{ + log_info("running 03_fs_readsync"); + int r = 0; uv_loop_t *loop = uv_default_loop(); - /* 1. Open file */ + /** + * Main Resources + * [official libuv documentation](http://docs.libuv.org/en/latest/index.html) *you should make use of the "Quick Search" box* + * [Unoffical libuv dox examples](https://github.com/thlorenz/libuv-dox/tree/master/examples) + * + * Resources + * [`uv_fs_open`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_open) + * [`uv_buf_init`](http://docs.libuv.org/en/latest/misc.html#c.uv_buf_init) + * [`uv_fs_read`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_read) + * [`uv_fs_close`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_close) + */ + + /** + * 1. Open file + * When opening a file pass O_RDONLY as the flags and S_IRUSR as the mode. + * All uv_fs_* methods pass back a signed integer that needs to be checked. If it is negative an error occurred. + * These checks have been added for you already, but pay close attention as in the future you will need to add them yourself. + */ uv_fs_t open_req; + r = uv_fs_open(loop, &open_req, filename, O_RDONLY, S_IRUSR, NULL); - if (r < 0) CHECK(r, "uv_fs_open"); + if (r < 0) + { + CHECK(r, "uv_fs_open"); + } /* 2. Create buffer and initialize it to turn it into a a uv_buf_t which adds length field */ - /* The + 1 is for the string null terminator, because log_report and log_info need it */ - /* Make sure you set your uv_buf_t size to BUF_SIZE instead of sizeof(buf) */ + /* The + 1 is for the string null terminator, because log_report and log_info need it */ + /* Make sure you set your uv_buf_t size to BUF_SIZE instead of sizeof(buf) */ char buf[BUF_SIZE + 1]; memset(buf, 0, sizeof(buf)); + uv_buf_t iov = uv_buf_init(buf, BUF_SIZE); /* 3. Use the file descriptor (the .result of the open_req) to read from the file into the buffer */ uv_fs_t read_req; + r = uv_fs_read(loop, &read_req, open_req.result, &iov, 1, 0, NULL); - if (r < 0) CHECK(r, "uv_fs_read"); + if (r < 0) + { + CHECK(r, "uv_fs_read"); + } /* 4. Report the contents of the buffer */ log_report("%s", buf); - log_info("%s", buf); /* 5. Close the file descriptor (`open_req.result`) */ uv_fs_t close_req; - - if (r < 0) CHECK(r, "uv_fs_close"); + r = uv_fs_close(loop, &close_req, open_req.result, NULL); + if (r < 0) + { + CHECK(r, "uv_fs_close"); + } /* always clean up your requests when you no longer need them to free unused memory */ uv_fs_req_cleanup(&open_req); diff --git a/src/04_fs_readasync.c b/src/04_fs_readasync.c index 16275ed..355c99d 100644 --- a/src/04_fs_readasync.c +++ b/src/04_fs_readasync.c @@ -7,9 +7,13 @@ static const char *filename = __MAGIC_FILE__; static uv_fs_t open_req; static uv_buf_t iov; -void read_cb(uv_fs_t* read_req) { +void read_cb(uv_fs_t *read_req) +{ int r = 0; - if (read_req->result < 0) CHECK(read_req->result, "uv_fs_read callback"); + if (read_req->result < 0) + { + CHECK(read_req->result, "uv_fs_read callback"); + } /* 4. Report the contents of the buffer */ log_report("%s", iov.base); @@ -17,25 +21,51 @@ void read_cb(uv_fs_t* read_req) { /* 5. Close the file descriptor */ uv_fs_t close_req; - if (r < 0) CHECK(r, "uv_fs_close"); + r = uv_fs_close(read_req->loop, &close_req, open_req.result, NULL); + if (r < 0) + { + CHECK(r, "uv_fs_close"); + } uv_fs_req_cleanup(&open_req); uv_fs_req_cleanup(read_req); uv_fs_req_cleanup(&close_req); } -int main() { +/** + * [`uv_fs_open`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_open) + * [`uv_buf_init`](http://docs.libuv.org/en/latest/misc.html#c.uv_buf_init) + * [`uv_fs_read`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_read) + * [`uv_fs_close`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_close) + * [`uv_read_cb`](http://docs.libuv.org/en/latest/stream.html#c.uv_read_cb) + * [`uv_fs_req_cleanup`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_req_cleanup) + */ +int main() +{ + log_info("running 04_fs_readasync"); + int r = 0; uv_loop_t *loop = uv_default_loop(); /* 1. Open file (synchronously) */ - if (r < 0) CHECK(r, "uv_fs_open"); + r = uv_fs_open(loop, &open_req, filename, O_RDONLY, S_IRUSR, NULL); + if (r < 0) + { + CHECK(r, "uv_fs_open"); + } /* 2. Create buffer and initialize it to turn it into a a uv_buf_t */ + char buf[BUF_SIZE + 1]; + memset(buf, 0, sizeof(buf)); + iov = uv_buf_init(buf, BUF_SIZE); /* 3. Use the file descriptor (the .result of the open_req) to read **aynchronously** from the file into the buffer */ uv_fs_t read_req; - if (r < 0) CHECK(r, "uv_fs_read"); + r = uv_fs_read(loop, &read_req, open_req.result, &iov, 1, 0, read_cb); + if (r < 0) + { + CHECK(r, "uv_fs_read"); + } uv_run(loop, UV_RUN_DEFAULT); diff --git a/src/05_fs_readasync_context.c b/src/05_fs_readasync_context.c index 0597e98..2093548 100644 --- a/src/05_fs_readasync_context.c +++ b/src/05_fs_readasync_context.c @@ -3,27 +3,40 @@ #define BUF_SIZE 37 static const char *filename = __MAGIC_FILE__; -typedef struct context_struct { +typedef struct context_struct +{ uv_fs_t *open_req; uv_buf_t iov; } context_t; -void read_cb(uv_fs_t* read_req) { +void read_cb(uv_fs_t *read_req) +{ int r = 0; - if (read_req->result < 0) CHECK(read_req->result, "uv_fs_read callback"); + if (read_req->result < 0) + { + CHECK(read_req->result, "uv_fs_read callback"); + } /* extracting our context from the read_req */ - context_t* context = read_req->data; + context_t *context = read_req->data; /* 4. Report the contents of the buffer */ log_report("%s", context->iov.base); log_info("%s", context->iov.base); - free(context->iov.base); + /** + * TODO: Find out why freeing isn't working here. + * 05_fs_readasync_context(65289,0x1ea364c00) malloc: *** error for object 0x16fdfef02: pointer being freed was not allocated + */ + // free(context->iov.base); /* 5. Close the file descriptor (synchronously) */ uv_fs_t close_req; - if (r < 0) CHECK(abs(r), "uv_fs_close"); + r = uv_fs_close(read_req->loop, &close_req, context->open_req->result, NULL); + if (r < 0) + { + CHECK(r, "uv_fs_close"); + } /* cleanup all requests and context */ uv_fs_req_cleanup(context->open_req); @@ -32,29 +45,58 @@ void read_cb(uv_fs_t* read_req) { free(context); } -void init(uv_loop_t *loop) { +void init(uv_loop_t *loop) +{ int r = 0; - /* No more globals, we need to malloc each request and pass it around for later cleanup */ + /** + * We need to malloc these requests because if we would use automatic variables instead, they would be + * "automatically" deallocated once we leave the init function body. + * However we need them to stay around since the read_cb will be invoked asynchronously. + */ uv_fs_t *open_req = malloc(sizeof(uv_fs_t)); uv_fs_t *read_req = malloc(sizeof(uv_fs_t)); context_t *context = malloc(sizeof(context_t)); - context->open_req = open_req; + context->open_req = open_req; /* 1. Open file */ - if (r < 0) CHECK(r, "uv_fs_open"); + r = uv_fs_open(loop, open_req, filename, O_RDONLY, S_IRUSR, NULL); + if (r < 0) + { + CHECK(r, "uv_fs_open"); + } /* 2. Create buffer and initialize it to turn it into a a uv_buf_t */ + char buf[BUF_SIZE + 1]; + memset(buf, 0, sizeof(buf)); + uv_buf_t iov = uv_buf_init(buf, BUF_SIZE); + context->iov = iov; /* allow us to access the context inside read_cb */ read_req->data = context; /* 3. Read from the file into the buffer */ - if (r < 0) CHECK(r, "uv_fs_read"); + // r = uv_fs_read(loop, read_req, open_req, &iov, 1, 0, read_cb); + r = uv_fs_read(loop, read_req, open_req->result, &iov, 1, 0, read_cb); + if (r < 0) + { + CHECK(r, "uv_fs_read"); + } } -int main() { +/** + * [`uv_fs_open`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_open) + * [`uv_buf_init`](http://docs.libuv.org/en/latest/misc.html#c.uv_buf_init) + * [`uv_fs_read`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_read) + * [`uv_fs_close`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_close) + * [`uv_read_cb`](http://docs.libuv.org/en/latest/stream.html#c.uv_read_cb) + * [`uv_fs_req_cleanup`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_req_cleanup) + */ +int main() +{ + log_info("running 05_fs_readasync_context"); + uv_loop_t *loop = uv_default_loop(); init(loop); diff --git a/src/06_fs_allasync.c b/src/06_fs_allasync.c index 49f2464..995f67b 100644 --- a/src/06_fs_allasync.c +++ b/src/06_fs_allasync.c @@ -3,58 +3,94 @@ #define BUF_SIZE 37 static const char *filename = __MAGIC_FILE__; +/** + * [`uv_fs_open`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_open) + * [`uv_buf_init`](http://docs.libuv.org/en/latest/misc.html#c.uv_buf_init) + * [`uv_fs_read`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_read) + * [`uv_fs_close`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_close) + * [`uv_read_cb`](http://docs.libuv.org/en/latest/stream.html#c.uv_read_cb) + * [`uv_fs_req_cleanup`](http://docs.libuv.org/en/latest/fs.html#c.uv_fs_req_cleanup) + */ + /* forward declarations */ -void open_cb(uv_fs_t*); -void read_cb(uv_fs_t*); -void close_cb(uv_fs_t*); +void open_cb(uv_fs_t *); +void read_cb(uv_fs_t *); +void close_cb(uv_fs_t *); -typedef struct context_struct { +typedef struct context_struct +{ uv_fs_t *open_req; uv_fs_t *read_req; uv_buf_t iov; } context_t; -void open_cb(uv_fs_t* open_req) { +void open_cb(uv_fs_t *open_req) +{ int r = 0; - if (open_req->result < 0) CHECK(open_req->result, "uv_fs_open callback"); + if (open_req->result < 0) + { + CHECK(open_req->result, "uv_fs_open callback"); + } - context_t* context = open_req->data; + context_t *context = open_req->data; /* 3. Create buffer and initialize it */ + char buf[BUF_SIZE + 1]; + memset(buf, 0, sizeof(buf)); + uv_buf_t iov = uv_buf_init(buf, BUF_SIZE); + context->iov = iov; /* 4. Setup read request */ - uv_fs_t *read_req = NULL; /* = malloc ... */ - context->read_req = NULL; /* = ? */ - read_req->data = NULL; /* = ? */ + uv_fs_t *read_req = malloc(sizeof(uv_fs_t)); + context->read_req = read_req; + read_req->data = context; /* 5. Read from the file into the buffer */ - if (r < 0) CHECK(r, "uv_fs_read"); + r = uv_fs_read(open_req->loop, read_req, open_req->result, &iov, 1, 0, read_cb); + if (r < 0) + { + CHECK(r, "uv_fs_read"); + } } -void read_cb(uv_fs_t* read_req) { - int r = 0;; - if (read_req->result < 0) CHECK(read_req->result, "uv_fs_read callback"); +void read_cb(uv_fs_t *read_req) +{ + int r = 0; + if (read_req->result < 0) + { + CHECK(read_req->result, "uv_fs_read callback"); + } - context_t* context = NULL; /* = ? */ + context_t *context = read_req->data; /* 6. Report the contents of the buffer */ log_report("%s", context->iov.base); log_info("%s", context->iov.base); - free(context->iov.base); + // free(context->iov.base); /* 7. Setup close request */ - uv_fs_t *close_req = NULL; - /* ? */ + // uv_fs_t close_req; + // close_req.data = context; + uv_fs_t *close_req = malloc(sizeof(uv_fs_t)); + close_req->data = context; /* 8. Close the file descriptor */ - if (r < 0) CHECK(r, "uv_fs_close"); + r = uv_fs_close(read_req->loop, close_req, context->open_req->result, close_cb); + if (r < 0) + { + CHECK(r, "uv_fs_close"); + } } -void close_cb(uv_fs_t* close_req) { - if (close_req->result < 0) CHECK(close_req->result, "uv_fs_close callback"); +void close_cb(uv_fs_t *close_req) +{ + if (close_req->result < 0) + { + CHECK(close_req->result, "uv_fs_close callback"); + } - context_t* context = NULL; + context_t *context = close_req->data; /* 9. Cleanup all requests and context */ uv_fs_req_cleanup(context->open_req); @@ -63,21 +99,28 @@ void close_cb(uv_fs_t* close_req) { free(context); } -void init(uv_loop_t *loop) { +void init(uv_loop_t *loop) +{ int r; - uv_fs_t *open_req = malloc(sizeof(uv_fs_t)); /* 1. Setup open request */ context_t *context = malloc(sizeof(context_t)); - context->open_req = open_req; + context->open_req = open_req; open_req->data = context; /* 2. Open file */ - if (r < 0) CHECK(r, "uv_fs_open"); + r = uv_fs_open(loop, open_req, filename, O_RDONLY, S_IRUSR, open_cb); + if (r < 0) + { + CHECK(r, "uv_fs_open"); + } } -int main() { +int main() +{ + log_info("running 06_fs_allasync"); + uv_loop_t *loop = uv_default_loop(); init(loop); diff --git a/src/07_tcp_echo_server.c b/src/07_tcp_echo_server.c index bdf0898..45e7500 100644 --- a/src/07_tcp_echo_server.c +++ b/src/07_tcp_echo_server.c @@ -1,37 +1,42 @@ #include "learnuv.h" #include -const static char* HOST = "0.0.0.0"; /* localhost */ -const static int PORT = 7000; -const static int NBUFS = 1; /* number of buffers we write at once */ +const static char *HOST = "0.0.0.0"; /* localhost */ +const static int PORT = 7001; +const static int NBUFS = 1; /* number of buffers we write at once */ static uv_tcp_t tcp_server; -typedef struct { +typedef struct +{ uv_write_t req; uv_buf_t buf; } write_req_t; /* forward declarations */ -static void close_cb(uv_handle_t* client); -static void server_close_cb(uv_handle_t*); -static void shutdown_cb(uv_shutdown_t*, int); +static void close_cb(uv_handle_t *client); +static void server_close_cb(uv_handle_t *); +static void shutdown_cb(uv_shutdown_t *, int); -static void alloc_cb(uv_handle_t*, size_t, uv_buf_t*); -static void read_cb(uv_stream_t*, ssize_t, const uv_buf_t*); -static void write_cb(uv_write_t*, int); +static void alloc_cb(uv_handle_t *, size_t, uv_buf_t *); +static void read_cb(uv_stream_t *, ssize_t, const uv_buf_t *); +static void write_cb(uv_write_t *, int); -static void close_cb(uv_handle_t* client) { +static void close_cb(uv_handle_t *client) +{ free(client); log_info("Closed connection"); } -static void shutdown_cb(uv_shutdown_t* req, int status) { - uv_close((uv_handle_t*) req->handle, close_cb); +static void shutdown_cb(uv_shutdown_t *req, int status) +{ + // http://docs.libuv.org/en/latest/handle.html#c.uv_close + uv_close((uv_handle_t *)req->handle, close_cb); free(req); } -static void onconnection(uv_stream_t *server, int status) { +static void onconnection(uv_stream_t *server, int status) +{ CHECK(status, "onconnection"); int r = 0; @@ -41,36 +46,52 @@ static void onconnection(uv_stream_t *server, int status) { log_info("Accepting Connection"); /* 4.1. Init client connection using `server->loop`, passing the client handle */ + // http://docs.libuv.org/en/latest/tcp.html#c.uv_tcp_init uv_tcp_t *client = malloc(sizeof(uv_tcp_t)); + r = uv_tcp_init(server->loop, client); CHECK(r, "uv_tcp_init"); /* 4.2. Accept the now initialized client connection */ - if (r) { + // http://docs.libuv.org/en/latest/stream.html#c.uv_accept + r = uv_accept(server, (uv_stream_t *)client); + if (r) + { log_error("trying to accept connection %d", r); shutdown_req = malloc(sizeof(uv_shutdown_t)); - r = uv_shutdown(shutdown_req, (uv_stream_t*) client, shutdown_cb); + r = uv_shutdown(shutdown_req, (uv_stream_t *)client, shutdown_cb); CHECK(r, "uv_shutdown"); } /* 5. Start reading data from client */ + // http://docs.libuv.org/en/latest/stream.html#c.uv_read_start + r = uv_read_start((uv_stream_t *)client, alloc_cb, read_cb); CHECK(r, "uv_read_start"); } -static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) { +static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) +{ /* libuv suggests a buffer size but leaves it up to us to create one of any size we see fit */ buf->base = malloc(size); buf->len = size; - if (buf->base == NULL) log_error("alloc_cb buffer didn't properly initialize"); + if (buf->base == NULL) + { + log_error("alloc_cb buffer didn't properly initialize"); + } } -static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { +static void read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) +{ int r = 0; uv_shutdown_t *shutdown_req; /* Errors or EOF */ - if (nread < 0) { - if (nread != UV_EOF) CHECK(nread, "read_cb"); + if (nread < 0) + { + if (nread != UV_EOF) + { + CHECK(nread, "read_cb"); + } /* Client signaled that all data has been sent, so we can close the connection and are done */ free(buf->base); @@ -81,19 +102,22 @@ static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { return; } - if (nread == 0) { + if (nread == 0) + { /* Everything OK, but nothing read and thus we don't write anything */ free(buf->base); return; } /* Check if we should quit the server which the client signals by sending "QUIT" */ - if (!strncmp("QUIT", buf->base, fmin(nread, 4))) { + if (!strncmp("QUIT", buf->base, fmin(nread, 4))) + { log_info("Closing the server"); free(buf->base); /* Before exiting we need to properly close the server via uv_close */ /* We can do this synchronously */ - uv_close((uv_handle_t*) &tcp_server, NULL); + // http://docs.libuv.org/en/latest/handle.html#c.uv_close + uv_close((uv_handle_t *)&tcp_server, NULL); log_info("Closed server, exiting"); exit(0); } @@ -102,38 +126,63 @@ static void read_cb(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { /* We wrap the write req and buf here in order to be able to clean them both later */ write_req_t *write_req = malloc(sizeof(write_req_t)); write_req->buf = uv_buf_init(buf->base, nread); + // https://docs.libuv.org/en/latest/stream.html#c.uv_write + r = uv_write(&write_req->req, client, &write_req->buf, NBUFS, write_cb); CHECK(r, "uv_write"); } -static void write_cb(uv_write_t *req, int status) { +static void write_cb(uv_write_t *req, int status) +{ CHECK(status, "write_cb"); log_info("Replied to client"); /* Since the req is the first field inside the wrapper write_req, we can just cast to it */ /* Basically we are telling C to include a bit more data starting at the same memory location, which in this case is our buf */ - write_req_t *write_req = (write_req_t*) req; + write_req_t *write_req = (write_req_t *)req; free(write_req->buf.base); free(write_req); } -int main() { +int main() +{ int r = 0; uv_loop_t *loop = uv_default_loop(); + // http://docs.libuv.org/en/latest/tcp.html + /* 1. Initialize TCP server */ + // http://docs.libuv.org/en/latest/tcp.html#c.uv_tcp_init r = uv_tcp_init(loop, &tcp_server); CHECK(r, "uv_tcp_init"); - /* 2. Bind to localhost:7000 */ + /* 2. Bind to localhost:7001 */ + // http://docs.libuv.org/en/latest/misc.html#c.uv_ip4_addr struct sockaddr_in addr; + r = uv_ip4_addr(HOST, PORT, &addr); CHECK(r, "uv_ip4_addr"); + // http://docs.libuv.org/en/latest/tcp.html#c.uv_tcp_bind + r = uv_tcp_bind(&tcp_server, (struct sockaddr *)&addr, AF_INET); CHECK(r, "uv_tcp_bind"); /* 3. Start listening */ /* uv_tcp_t inherits uv_stream_t so casting is ok */ + // http://docs.libuv.org/en/latest/tcp.html + // http://docs.libuv.org/en/latest/stream.html + // http://docs.libuv.org/en/latest/handle.html + // https://docs.libuv.org/en/latest/stream.html#c.uv_listen + r = uv_listen((uv_stream_t *)&tcp_server, SOMAXCONN, onconnection); CHECK(r, "uv_listen"); + + /* + * Use `nc localhost 7001` to test your server (finish via Ctrl-D) and/or stop the server by sending QUIT. +nc localhost 7001 +echo 'Hello, Netcat!' | nc localhost 7001 + + * You can also send entire files, i.e. `cat package.json | netcat localhost 7001`. +cat package.json | netcat localhost 7001 + */ log_info("Listening on %s:%d", HOST, PORT); uv_run(loop, UV_RUN_DEFAULT); diff --git a/src/08_horse_race.c b/src/08_horse_race.c index 9712414..3c53e72 100644 --- a/src/08_horse_race.c +++ b/src/08_horse_race.c @@ -6,49 +6,54 @@ #include #include -#define HORSE_WIDTH 29 +#define HORSE_WIDTH 29 #define HORSE_HEIGHT 11 -#define TRACK_WIDTH 110 -#define TRACKS 5 -#define THREADS "5" +#define TRACK_WIDTH 110 // 110 +#define TRACKS 5 // 5 +#define THREADS "5" // 5 -#define DRAW 1 +// http://tldp.org/HOWTO/NCURSES-Programming-HOWTO +// http://www.gnu.org/software/ncurses/ncurses.html +#define DRAW 0 // curses isn't working for me. Set to 0 to see log output. -typedef struct { +typedef struct +{ uv_async_t async; - char* name; + char *name; int color; int track; int speed; int position; } horse_t; -const static char* horse_pic[HORSE_HEIGHT] = { - " ,***, ", - " ,***` *==--", - " ,**`( '| ", - " ,**@ /v_/ ", - " ,*.-'''--*** '@@__ ", - " **/ |__`v ", - " .*'v | v / // ", - " ,*' > .'----v | [/ ", - " < <<` || ", - " `vvv || ", - " )vv )v " -}; +const static char *horse_pic[HORSE_HEIGHT] = { + " ,***, ", + " ,***` *==--", + " ,**`( '| ", + " ,**@ /v_/ ", + " ,*.-'''--*** '@@__ ", + " **/ |__`v ", + " .*'v | v / // ", + " ,*' > .'----v | [/ ", + " < <<` || ", + " `vvv || ", + " )vv )v "}; static horse_t horses[TRACKS] = { - { .name = "bnoordhuis " , .color = 1, .track = 0, .position = 0 }, - { .name = "piscisaureus" , .color = 2, .track = 1, .position = 0 }, - { .name = "ry " , .color = 3, .track = 2, .position = 0 }, - { .name = "saghul " , .color = 4, .track = 3, .position = 0 }, - { .name = "indutny " , .color = 5, .track = 4, .position = 0 } -}; + {.name = "bnoordhuis ", .color = 1, .track = 0, .position = 0}, + {.name = "piscisaureus", .color = 2, .track = 1, .position = 0}, + {.name = "ry ", .color = 3, .track = 2, .position = 0}, + {.name = "saghul ", .color = 4, .track = 3, .position = 0}, + {.name = "indutny ", .color = 5, .track = 4, .position = 0}}; int placement = 1; -static void load_color_palette() { - if (!has_colors()) return; +static void load_color_palette() +{ + if (!has_colors()) + { + return; + } use_default_colors(); start_color(); @@ -59,7 +64,8 @@ static void load_color_palette() { init_pair(5, COLOR_YELLOW, -1); } -static void init_screen() { +static void init_screen() +{ initscr(); curs_set(FALSE); raw(); @@ -68,67 +74,103 @@ static void init_screen() { load_color_palette(); } -void draw_horse(horse_t *horse) { - if (!DRAW) { +void draw_horse(horse_t *horse) +{ + if (!DRAW) + { log_info("Horse %s\ttrack: %d\t position: %d", horse->name, horse->track, horse->position); return; } int i; attrset(COLOR_PAIR(horse->color)); - for (i = 0; i < HORSE_HEIGHT; i++) { + for (i = 0; i < HORSE_HEIGHT; i++) + { mvprintw((horse->track * HORSE_HEIGHT) + i, horse->position, horse_pic[i]); } refresh(); } -void progress_cb(uv_async_t* async) { +void progress_cb(uv_async_t *async) +{ horse_t *horse = async->data; draw_horse(horse); + return; } -void race_cb(uv_work_t* work_req) { +void race_cb(uv_work_t *work_req) +{ + int r = 0; horse_t *horse = work_req->data; double speed = 10; int i; - for (i = 0; i < TRACK_WIDTH; i++) { + for (i = 0; i < TRACK_WIDTH; i++) + { horse->position++; /* 3. Send progress report so we can redraw the position of the horse every time its position changed */ + // http://docs.libuv.org/en/latest/async.html#c.uv_async_send + r = uv_async_send(&horse->async); + CHECK(r, "uv_async_send"); speed += ((rand() % 5) - 2); /* whiplash in case our horse starts slowing down too much */ - if (speed < 5) speed = 7; + if (speed < 5) + { + speed = 7; + } - usleep(1E6/speed); + usleep(1E6 / speed); } } -void finished_race_cb(uv_work_t* work_req, int status) { +void finished_race_cb(uv_work_t *work_req, int status) +{ CHECK(status, "finished_race_cb"); horse_t *horse = work_req->data; - if (!DRAW) log_info("Horse %s finished race", horse->name); - log_report("Horse %s finished in place %d", horse->name, placement++); - uv_close((uv_handle_t*) &horse->async, NULL); + + if (!DRAW) + { + log_info("Horse %s finished in place %d\ttrack: %d", horse->name, placement, horse->track); + } + log_report("Horse %s finished in place %d", horse->name, placement); + + placement++; + + // https://docs.libuv.org/en/latest/handle.html#c.uv_close + uv_close((uv_handle_t *)&horse->async, NULL); } -void add_horse(uv_loop_t* loop, int track) { +void add_horse(uv_loop_t *loop, int track) +{ + int r = 0; horse_t *horse = horses + track; - uv_work_t* work_req = malloc(sizeof(uv_work_t)); + // https://docs.libuv.org/en/latest/threadpool.html + uv_work_t *work_req = malloc(sizeof(uv_work_t)); work_req->data = horse; horse->async.data = horse; /* 1. Init async worker (attached to our horse) passing a callback to report progress */ + // http://docs.libuv.org/en/latest/async.html#c.uv_async_init + r = uv_async_init(loop, &horse->async, progress_cb); + CHECK(r, "uv_async_init"); /* 2. Queue work for our worker passing the right callbacks */ + // http://docs.libuv.org/en/latest/threadpool.html#c.uv_queue_work + r = uv_queue_work(loop, (uv_work_t *)work_req, race_cb, finished_race_cb); + CHECK(r, "uv_async_init"); - if (!DRAW) log_info("Queued horse %s on track: %d", horse->name, horse->track); + if (!DRAW) + { + log_info("Queued horse %s on track: %d", horse->name, horse->track); + } } -int main(void) { +int main(void) +{ int i; /* Ensure that each horse gets its own thread, the default libuv threadpool size is 4 */ @@ -136,16 +178,23 @@ int main(void) { srand(time(NULL)); - uv_loop_t* loop = uv_default_loop(); + uv_loop_t *loop = uv_default_loop(); - if (DRAW) init_screen(); + if (DRAW) + { + init_screen(); + } - for (i = 0; i < TRACKS; i++) { + for (i = 0; i < TRACKS; i++) + { add_horse(loop, i); } uv_run(loop, UV_RUN_DEFAULT); - if (DRAW) endwin(); + if (DRAW) + { + endwin(); + } return 0; } diff --git a/src/epam_workshop.c b/src/epam_workshop.c new file mode 100644 index 0000000..62f3900 --- /dev/null +++ b/src/epam_workshop.c @@ -0,0 +1,34 @@ +#include "learnuv.h" + +static const char *filename = __MAGIC_FILE__; + +void async_hello_world_cb(uv_work_t *work_req) +{ + log_info("Hello world"); + return; +} + +void finish_cb(uv_work_t *work_req, int status) +{ + CHECK(status, "finish_cb"); + return; +} + +int main() +{ + setenv("UV_THREADPOOL_SIZE", "0", 1); + + int r; + // event-driven + uv_loop_t *loop = uv_default_loop(); + + // https://docs.libuv.org/en/latest/threadpool.html + uv_work_t *work_req = malloc(sizeof(uv_work_t)); + + r = uv_queue_work(loop, work_req, async_hello_world_cb, NULL); + CHECK(r, "uv_async_init"); + + uv_run(loop, UV_RUN_DEFAULT); + + return 0; +} diff --git a/src/event_loop_sandbox/libuv-event-loop.png b/src/event_loop_sandbox/libuv-event-loop.png new file mode 100644 index 0000000..d64083c Binary files /dev/null and b/src/event_loop_sandbox/libuv-event-loop.png differ diff --git a/src/event_loop_sandbox/node-event-loop.png b/src/event_loop_sandbox/node-event-loop.png new file mode 100644 index 0000000..12b3276 Binary files /dev/null and b/src/event_loop_sandbox/node-event-loop.png differ diff --git a/src/interactive_horse_race/interactive_horse_race.c b/src/interactive_horse_race/interactive_horse_race.c index 2960fe9..3f0dccd 100644 --- a/src/interactive_horse_race/interactive_horse_race.c +++ b/src/interactive_horse_race/interactive_horse_race.c @@ -7,28 +7,34 @@ #define to_s(x) #x #define THREADS to_s(TRACKS) -#define HOST "0.0.0.0" /* localhost */ -#define PORT 7000 +#define HOST "0.0.0.0" /* localhost */ +#define PORT 7001 -static void start_game(uv_loop_t* loop, luv_game_t* game) { +static void start_game(uv_loop_t *loop, luv_game_t *game) +{ log_info("Initializing track"); track_init(loop, game->server->clients, game->server->num_clients); game->in_progress = 1; game->delay = DELAY; } -static void question_handler(uv_idle_t* handle) { +static void question_handler(uv_idle_t *handle) +{ luv_game_t *game = handle->data; luv_server_t *server = game->server; - if (!game->in_progress) { - if (server->num_clients < MAX_CLIENTS) return; + if (!game->in_progress) + { + if (server->num_clients < MAX_CLIENTS) + return; log_info("Starting the race"); luv_server_broadcast(server, "\nAll tracks filled, let the race begin!\n"); start_game(handle->loop, game); } - if (game->question_asked) { - if (--game->time_to_answer > 0) return; + if (game->question_asked) + { + if (--game->time_to_answer > 0) + return; luv_server_broadcast(server, "\nWay too slow guys! Next question.\n"); game->question_asked = 0; } @@ -40,7 +46,8 @@ static void question_handler(uv_idle_t* handle) { luv_server_broadcast(server, "\n%s\n ? ", game->question.question); } -static void onclient_connected(luv_client_t* client, int total_connections) { +static void onclient_connected(luv_client_t *client, int total_connections) +{ luv_server_t *server = client->server; /* todo: update track if client gets moved to different slot */ @@ -51,23 +58,25 @@ static void onclient_connected(luv_client_t* client, int total_connections) { log_info("New player, %d total now.", total_connections); luv_server_broadcast(client->server, - "Welcome player %d!\nWe now have a total of %d players.\n", - client->id, total_connections); + "Welcome player %d!\nWe now have a total of %d players.\n", + client->id, total_connections); char client_msg[MAX_MSG]; snprintf(client_msg, MAX_MSG, - "Welcome to the game, you are on track %d\n", player->track + 1); + "Welcome to the game, you are on track %d\n", player->track + 1); luv_server_send(server, client, client_msg, strlen(client_msg)); } -static void onclient_disconnected(luv_client_t* client, int total_connections) { +static void onclient_disconnected(luv_client_t *client, int total_connections) +{ log_info("Player %d quit, %d total now.", client->id, total_connections); luv_server_broadcast(client->server, - "Player quit %d :(\nWe have %d players left.\n", - client->id, total_connections); + "Player quit %d :(\nWe have %d players left.\n", + client->id, total_connections); } -static void onclient_msg(luv_client_msg_t* msg, luv_onclient_msg_processed respond) { +static void onclient_msg(luv_client_msg_t *msg, luv_onclient_msg_processed respond) +{ luv_client_t *client = msg->client; log_info("Got message %s from client %d", msg->buf, msg->client->id); @@ -75,7 +84,8 @@ static void onclient_msg(luv_client_msg_t* msg, luv_onclient_msg_processed respo luv_game_t *game = server->data; luv_player_t *player = client->data; - if (!game->in_progress) { + if (!game->in_progress) + { respond(msg, "Be patient, the game hasn't started yet.\n"); return; } @@ -87,20 +97,24 @@ static void onclient_msg(luv_client_msg_t* msg, luv_onclient_msg_processed respo char res[MAX_MSG]; - if (!strncasecmp(correct, given, fmin(correct_len, given_len))) { + if (!strncasecmp(correct, given, fmin(correct_len, given_len))) + { player->speed++; sprintf(res, "Your answer is correct! Your speed is now %d\n", player->speed); game->question_asked = 0; - } else { + } + else + { player->speed = fmax(0, player->speed - 1); - sprintf(res, "Your answer is wrong! Your speed is now %d\n\n%s\n ? ",player->speed, game->question.question); + sprintf(res, "Your answer is wrong! Your speed is now %d\n\n%s\n ? ", player->speed, game->question.question); } respond(msg, res); } -int main(void) { +int main(void) +{ uv_loop_t *loop = uv_default_loop(); /* Ensure that each horse gets its own thread, the default libuv threadpool size is 4 */ @@ -113,19 +127,13 @@ int main(void) { log_info("Creating server"); luv_server_t server; luv_server_init( - &server - , loop - , HOST - , PORT - , onclient_connected - , onclient_disconnected - , onclient_msg); + &server, loop, HOST, PORT, onclient_connected, onclient_disconnected, onclient_msg); log_info("Starting server"); luv_server_start(&server, loop); log_info("Initializing game loop"); - luv_game_t game = { .server = &server, .delay = DELAY }; + luv_game_t game = {.server = &server, .delay = DELAY}; server.data = &game; uv_idle_t question_handle; diff --git a/src/interactive_horse_race/interactive_horse_race.h b/src/interactive_horse_race/interactive_horse_race.h index c07cf91..ac3b3f4 100644 --- a/src/interactive_horse_race/interactive_horse_race.h +++ b/src/interactive_horse_race/interactive_horse_race.h @@ -6,7 +6,7 @@ */ /* set to 0 in order to not draw but log messages instead */ -#define DRAW 1 +#define DRAW 0 #define MAX_MSG 1024 #if DRAW == 1 @@ -15,8 +15,8 @@ #include "learnuv.h" -#define DELAY 1E6 -#define MAX_SPEED 20 +#define DELAY 1E6 +#define MAX_SPEED 20 #define QUESTION_LEN 256 /* @@ -25,69 +25,69 @@ #define MAX_CLIENTS 2 -#define luv_server_broadcast(s, fmt, ...) do { \ - int i, __len; \ - char msg[MAX_MSG]; \ - snprintf(msg, MAX_MSG, fmt, ##__VA_ARGS__); \ - __len = strlen(msg); \ - for (i = 0; i < (s)->num_clients; i++) \ - luv_server_send((s), (s)->clients[i], msg, __len); \ -} while(0) +#define luv_server_broadcast(s, fmt, ...) \ + do \ + { \ + int i, __len; \ + char msg[MAX_MSG]; \ + snprintf(msg, MAX_MSG, fmt, ##__VA_ARGS__); \ + __len = strlen(msg); \ + for (i = 0; i < (s)->num_clients; i++) \ + luv_server_send((s), (s)->clients[i], msg, __len); \ + } while (0) typedef struct luv_server_s luv_server_t; -typedef struct { - uv_tcp_t connection; - int id; - int slot; - luv_server_t* server; - void* data; +typedef struct +{ + uv_tcp_t connection; + int id; + int slot; + luv_server_t *server; + void *data; } luv_client_t; -typedef struct { - char* buf; - size_t len; - luv_client_t* client; +typedef struct +{ + char *buf; + size_t len; + luv_client_t *client; } luv_client_msg_t; -typedef void (*luv_onclient_msg_processed)(luv_client_msg_t*, char*); -typedef void (*luv_onclient_msg)(luv_client_msg_t*, luv_onclient_msg_processed); -typedef void (*luv_onclient_connected)(luv_client_t*, int); -typedef void (*luv_onclient_disconnected)(luv_client_t*, int); +typedef void (*luv_onclient_msg_processed)(luv_client_msg_t *, char *); +typedef void (*luv_onclient_msg)(luv_client_msg_t *, luv_onclient_msg_processed); +typedef void (*luv_onclient_connected)(luv_client_t *, int); +typedef void (*luv_onclient_disconnected)(luv_client_t *, int); /* server inherits uv_tcp_t since its the first field */ -struct luv_server_s { - uv_tcp_t tcp; - const char* host; - int port; - luv_client_t* clients[MAX_CLIENTS]; - int num_clients; - int ids; - void* data; +struct luv_server_s +{ + uv_tcp_t tcp; + const char *host; + int port; + luv_client_t *clients[MAX_CLIENTS]; + int num_clients; + int ids; + void *data; /* events */ luv_onclient_connected onclient_connected; luv_onclient_disconnected onclient_disconnected; luv_onclient_msg onclient_msg; }; -void luv_server_send(luv_server_t* self, luv_client_t* client, char* msg, int len); -void luv_server_destroy(luv_server_t*); -void luv_server_start(luv_server_t*, uv_loop_t*); +void luv_server_send(luv_server_t *self, luv_client_t *client, char *msg, int len); +void luv_server_destroy(luv_server_t *); +void luv_server_start(luv_server_t *, uv_loop_t *); void luv_server_init( - luv_server_t* self - , uv_loop_t *loop - , const char* host - , int port - , luv_onclient_connected onclient_connected - , luv_onclient_disconnected onclient_disconnected - , luv_onclient_msg onclient_msg); + luv_server_t *self, uv_loop_t *loop, const char *host, int port, luv_onclient_connected onclient_connected, luv_onclient_disconnected onclient_disconnected, luv_onclient_msg onclient_msg); /* * Questions */ -typedef struct { +typedef struct +{ char question[QUESTION_LEN]; char answer[QUESTION_LEN]; } luv_question_t; @@ -99,15 +99,16 @@ luv_question_t luv_questions_get(); * Game */ -typedef struct { - char* name; +typedef struct +{ + char *name; int color; int track; int position; } luv_horse_t; - -typedef struct { +typedef struct +{ luv_client_t *client; luv_horse_t *horse; int color; @@ -116,8 +117,9 @@ typedef struct { int position; } luv_player_t; -typedef struct { - luv_server_t* server; +typedef struct +{ + luv_server_t *server; int in_progress; int question_asked; luv_question_t question; @@ -129,7 +131,7 @@ typedef struct { * Track */ -void track_handler(uv_idle_t*); -void track_init(uv_loop_t*, luv_client_t**, int); +void track_handler(uv_idle_t *); +void track_init(uv_loop_t *, luv_client_t **, int); #endif diff --git a/src/interactive_horse_race/questions.c b/src/interactive_horse_race/questions.c index 34044fc..baa3823 100644 --- a/src/interactive_horse_race/questions.c +++ b/src/interactive_horse_race/questions.c @@ -8,51 +8,60 @@ luv_question_t math_questions[MATH_QUESTIONS_LEN]; luv_question_t conversion_questions[CONVERSION_QUESTIONS_LEN]; const luv_question_t canned_questions[CANNED_QUESTIONS_LEN] = { - { "You wake up in a forrest and are surrounded by vines. A gate is to the north." , "N" } , - { "What is the library that powers Node.js" , "libuv" }, - { "What is the language libuv is implemented in", "C" }, - { "What is the language v8 is implemented in", "C++" }, - { "At what conference was learnuv first used as a workshopper", "campjs" }, - { "What is the first name of the campjs curator", "Tim" }, - { "What is the first name of the Node.js creator", "Ryan" }, - { "C function used to allocate memory", "malloc" }, - { "C function used to release memory", "free" }, + {"You wake up in a forrest and are surrounded by vines. A gate is to the north.", "N"}, + {"What is the library that powers Node.js", "libuv"}, + {"What is the language libuv is implemented in", "C"}, + {"What is the language v8 is implemented in", "C++"}, + {"At what conference was learnuv first used as a workshopper", "campjs"}, + {"What is the first name of the campjs curator", "Tim"}, + {"What is the first name of the Node.js creator", "Ryan"}, + {"C function used to allocate memory", "malloc"}, + {"C function used to release memory", "free"}, }; -static luv_question_t get_canned_question() { +static luv_question_t get_canned_question() +{ return canned_questions[rand() % CANNED_QUESTIONS_LEN]; } -static luv_question_t get_conversion_question() { +static luv_question_t get_conversion_question() +{ return conversion_questions[rand() % CONVERSION_QUESTIONS_LEN]; } -static luv_question_t get_math_question() { +static luv_question_t get_math_question() +{ return math_questions[rand() % MATH_QUESTIONS_LEN]; } -static void init_math_questions() { +static void init_math_questions() +{ static const char *ops = "+-*"; int i, p1, p2, result, max; char op; - luv_question_t* qs; + luv_question_t *qs; - for (i = 0; i < MATH_QUESTIONS_LEN; i++) { + for (i = 0; i < MATH_QUESTIONS_LEN; i++) + { op = ops[rand() % 3]; max = op == '*' ? 10 : 100; p1 = rand() % max; p2 = rand() % max; - switch(op) { - case '+' : { - result = p1 + p2; - break; - } - case '-' : { - result = p1 - p2; - break; - } - default : result = p1 * p2; + switch (op) + { + case '+': + { + result = p1 + p2; + break; + } + case '-': + { + result = p1 - p2; + break; + } + default: + result = p1 * p2; } qs = &math_questions[i]; @@ -61,11 +70,13 @@ static void init_math_questions() { } } -static void init_conversion_questions() { +static void init_conversion_questions() +{ int i; - luv_question_t* qs; + luv_question_t *qs; - for (i = 0; i < CONVERSION_QUESTIONS_LEN; i += 2) { + for (i = 0; i < CONVERSION_QUESTIONS_LEN; i += 2) + { qs = &conversion_questions[i]; snprintf(qs->question, QUESTION_LEN, "%d converted to HEXADECIMAL", i); @@ -77,21 +88,27 @@ static void init_conversion_questions() { } } -void luv_questions_init() { +void luv_questions_init() +{ init_math_questions(); init_conversion_questions(); } -luv_question_t luv_questions_get() { +luv_question_t luv_questions_get() +{ int r = rand() % 5; - switch (r) { - case 0 : return get_canned_question(); - - /* the conversion questions are kinda hard, so we favor math questions ;) */ - case 1 : - case 2 : - case 3 : return get_math_question(); - - default : return get_conversion_question(); + switch (r) + { + case 0: + return get_canned_question(); + + /* the conversion questions are kinda hard, so we favor math questions ;) */ + case 1: + case 2: + case 3: + return get_math_question(); + + default: + return get_conversion_question(); } } diff --git a/src/interactive_horse_race/tcp_server.c b/src/interactive_horse_race/tcp_server.c index 03c3ac5..50e3ff4 100644 --- a/src/interactive_horse_race/tcp_server.c +++ b/src/interactive_horse_race/tcp_server.c @@ -1,45 +1,51 @@ #include "interactive_horse_race.h" /* forward declarations */ -static void close_cb(uv_handle_t* client); -static void client_shutdown_cb(uv_shutdown_t*, int); -static void shutdown_cb(uv_shutdown_t*, int); +static void close_cb(uv_handle_t *client); +static void client_shutdown_cb(uv_shutdown_t *, int); +static void shutdown_cb(uv_shutdown_t *, int); -static void alloc_cb(uv_handle_t*, size_t, uv_buf_t*); -static void read_cb(uv_stream_t*, ssize_t, const uv_buf_t*); -static void onclient_msg_processed(luv_client_msg_t*, char*); +static void alloc_cb(uv_handle_t *, size_t, uv_buf_t *); +static void read_cb(uv_stream_t *, ssize_t, const uv_buf_t *); +static void onclient_msg_processed(luv_client_msg_t *, char *); -static void close_cb(uv_handle_t* client) { +static void close_cb(uv_handle_t *client) +{ free(client->data); // free the player variable free(client); log_info("Closed connection"); } -static void shutdown_cb(uv_shutdown_t* req, int status) { - uv_close((uv_handle_t*) req->handle, close_cb); +static void shutdown_cb(uv_shutdown_t *req, int status) +{ + uv_close((uv_handle_t *)req->handle, close_cb); free(req); } -static void client_shutdown_cb(uv_shutdown_t* req, int status) { - uv_close((uv_handle_t*) req->handle, close_cb); +static void client_shutdown_cb(uv_shutdown_t *req, int status) +{ + uv_close((uv_handle_t *)req->handle, close_cb); /* todo: clean client (req->data) and remove it from it's server */ free(req); } -static void write_cb(uv_write_t* req, int status) { +static void write_cb(uv_write_t *req, int status) +{ CHECK(status, "write_cb"); free(req->bufs); free(req); } -static void disconnect(luv_client_t* client) { +static void disconnect(luv_client_t *client) +{ int r; - luv_server_t* server = client->server; + luv_server_t *server = client->server; int last_slot = server->num_clients - 1; /* unless the last client quit we move the last client into its slot * in order to keep the clients array contiguous */ - if (client->slot < last_slot) { + if (client->slot < last_slot) + { server->clients[client->slot] = server->clients[last_slot]; server->clients[client->slot]->slot = client->slot; } @@ -47,33 +53,36 @@ static void disconnect(luv_client_t* client) { server->num_clients--; server->onclient_disconnected(client, server->num_clients); - uv_shutdown_t* shutdown_req = malloc(sizeof(uv_shutdown_t)); + uv_shutdown_t *shutdown_req = malloc(sizeof(uv_shutdown_t)); shutdown_req->data = client; - r = uv_shutdown(shutdown_req, (uv_stream_t*) client, client_shutdown_cb); + r = uv_shutdown(shutdown_req, (uv_stream_t *)client, client_shutdown_cb); CHECK(r, "uv_shutdown"); } -static void onconnection(uv_stream_t *tcp, int status) { +static void onconnection(uv_stream_t *tcp, int status) +{ CHECK(status, "onconnection"); - luv_server_t* server = (luv_server_t*)tcp; + luv_server_t *server = (luv_server_t *)tcp; int r; /* Accept client connection */ log_info("Accepting Connection"); - if (server->num_clients == MAX_CLIENTS) { + if (server->num_clients == MAX_CLIENTS) + { log_info("exceeded allowed number of clients"); /* todo: Ideally we'd allow the client to connect, tell it and disconnect */ return; } luv_client_t *client = malloc(sizeof(luv_client_t)); - r = uv_tcp_init(tcp->loop, (uv_tcp_t*)client); + r = uv_tcp_init(tcp->loop, (uv_tcp_t *)client); CHECK(r, "uv_tcp_init"); - r = uv_accept(tcp, (uv_stream_t*) client); - if (r) { + r = uv_accept(tcp, (uv_stream_t *)client); + if (r) + { log_error("trying to accept connection %d", r); disconnect(client); return; @@ -81,121 +90,127 @@ static void onconnection(uv_stream_t *tcp, int status) { server->clients[server->num_clients] = client; client->server = server; - client->slot = server->num_clients; - client->id = server->ids++; + client->slot = server->num_clients; + client->id = server->ids++; server->num_clients++; server->onclient_connected(client, server->num_clients); /* Start reading data from client */ - r = uv_read_start((uv_stream_t*) client, alloc_cb, read_cb); + r = uv_read_start((uv_stream_t *)client, alloc_cb, read_cb); CHECK(r, "uv_read_start"); } -static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) { +static void alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) +{ buf->base = malloc(size); buf->len = size; - if (buf->base == NULL) log_error("alloc_cb buffer didn't properly initialize"); + if (buf->base == NULL) + log_error("alloc_cb buffer didn't properly initialize"); } -static void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { - luv_client_t *client = (luv_client_t*) stream; - luv_server_t* server = client->server; +static void read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +{ + luv_client_t *client = (luv_client_t *)stream; + luv_server_t *server = client->server; /* Errors or EOF */ - if (nread < 0) { - if (nread != UV_EOF) CHECK(nread, "read_cb"); + if (nread < 0) + { + if (nread != UV_EOF) + CHECK(nread, "read_cb"); free(buf->base); disconnect(client); return; } - if (nread == 0) { + if (nread == 0) + { free(buf->base); return; } luv_client_msg_t *msg = malloc(sizeof(luv_client_msg_t)); - msg->buf = buf->base; - msg->len = nread; + msg->buf = buf->base; + msg->len = nread; msg->client = client; server->onclient_msg(msg, onclient_msg_processed); } -static void onclient_msg_processed(luv_client_msg_t* msg, char* response) { +static void onclient_msg_processed(luv_client_msg_t *msg, char *response) +{ int r; - uv_write_t* write_req = malloc(sizeof(uv_write_t)); + uv_write_t *write_req = malloc(sizeof(uv_write_t)); int len = strlen(response); uv_buf_t buf = uv_buf_init(response, len); - r = uv_write(write_req, (uv_stream_t*) msg->client, &buf, 1, write_cb); + r = uv_write(write_req, (uv_stream_t *)msg->client, &buf, 1, write_cb); free(msg->buf); } -void luv_server_send(luv_server_t* self, luv_client_t* client, char* msg, int len) +void luv_server_send(luv_server_t *self, luv_client_t *client, char *msg, int len) { int r; - uv_write_t* write_req; + uv_write_t *write_req; - if (client == NULL) { + if (client == NULL) + { log_warn("Client %d was not properly initialized, cannot send message to it.", client->id); return; } uv_buf_t buf = uv_buf_init(msg, len); write_req = malloc(sizeof(uv_write_t)); - r = uv_write(write_req, (uv_stream_t*) client, &buf, 1, write_cb); + r = uv_write(write_req, (uv_stream_t *)client, &buf, 1, write_cb); CHECK(r, "uv_write"); } -void luv_server_destroy(luv_server_t* self) { +void luv_server_destroy(luv_server_t *self) +{ int r, i; uv_shutdown_t *shutdown_req; - for (i = 0; i < self->num_clients; i++) { + for (i = 0; i < self->num_clients; i++) + { shutdown_req = malloc(sizeof(uv_shutdown_t)); shutdown_req->data = self->clients[i]; - r = uv_shutdown(shutdown_req, (uv_stream_t*) self->clients[i], shutdown_cb); + r = uv_shutdown(shutdown_req, (uv_stream_t *)self->clients[i], shutdown_cb); CHECK(r, "uv_shutdown"); } - uv_close((uv_handle_t*) self, NULL); + uv_close((uv_handle_t *)self, NULL); } -void luv_server_start(luv_server_t* self, uv_loop_t *loop) { - int r = uv_listen((uv_stream_t*) self, SOMAXCONN, onconnection); +void luv_server_start(luv_server_t *self, uv_loop_t *loop) +{ + int r = uv_listen((uv_stream_t *)self, SOMAXCONN, onconnection); CHECK(r, "uv_listen"); log_info("Listening on %s:%d", self->host, self->port); } void luv_server_init( - luv_server_t* self - , uv_loop_t *loop - , const char* host - , int port - , luv_onclient_connected onclient_connected - , luv_onclient_disconnected onclient_disconnected - , luv_onclient_msg onclient_msg) { + luv_server_t *self, uv_loop_t *loop, const char *host, int port, luv_onclient_connected onclient_connected, luv_onclient_disconnected onclient_disconnected, luv_onclient_msg onclient_msg) +{ int r; - self->host = host; - self->port = port; - self->num_clients = 0; - self->onclient_connected = onclient_connected; + self->host = host; + self->port = port; + self->num_clients = 0; + self->onclient_connected = onclient_connected; self->onclient_disconnected = onclient_disconnected; - self->onclient_msg = onclient_msg; + self->onclient_msg = onclient_msg; /* Initialize TCP server */ r = uv_tcp_init(loop, &self->tcp); CHECK(r, "uv_tcp_init"); - /* Bind to localhost:7000 */ + /* Bind to localhost:7001 */ struct sockaddr_in addr; r = uv_ip4_addr(host, port, &addr); CHECK(r, "uv_ip4_addr"); - r = uv_tcp_bind(&self->tcp, (struct sockaddr*) &addr, AF_INET); + r = uv_tcp_bind(&self->tcp, (struct sockaddr *)&addr, AF_INET); CHECK(r, "uv_tcp_bind"); } diff --git a/src/interactive_horse_race/track.c b/src/interactive_horse_race/track.c index b3c185b..fa6e799 100644 --- a/src/interactive_horse_race/track.c +++ b/src/interactive_horse_race/track.c @@ -5,34 +5,34 @@ #include #include -#define HORSE_WIDTH 29 +#define HORSE_WIDTH 29 #define HORSE_HEIGHT 11 -#define TRACK_WIDTH 110 - -const static char* horse_pic[HORSE_HEIGHT] = { - " ,***, ", - " ,***` *==--", - " ,**`( '| ", - " ,**@ /v_/ ", - " ,*.-'''--*** '@@__ ", - " **/ |__`v ", - " .*'v | v / // ", - " ,*' > .'----v | [/ ", - " < <<` || ", - " `vvv || ", - " )vv )v " -}; +#define TRACK_WIDTH 110 + +const static char *horse_pic[HORSE_HEIGHT] = { + " ,***, ", + " ,***` *==--", + " ,**`( '| ", + " ,**@ /v_/ ", + " ,*.-'''--*** '@@__ ", + " **/ |__`v ", + " .*'v | v / // ", + " ,*' > .'----v | [/ ", + " < <<` || ", + " `vvv || ", + " )vv )v "}; static luv_horse_t horses[] = { - { .name = "bnoordhuis " , .color = 1, .track = 0, .position = 0 }, - { .name = "piscisaureus" , .color = 2, .track = 1, .position = 0 }, - { .name = "ry " , .color = 3, .track = 2, .position = 0 }, - { .name = "saghul " , .color = 4, .track = 3, .position = 0 }, - { .name = "indutny " , .color = 5, .track = 4, .position = 0 } -}; - -static void load_color_palette() { - if (!has_colors()) return; + {.name = "bnoordhuis ", .color = 1, .track = 0, .position = 0}, + {.name = "piscisaureus", .color = 2, .track = 1, .position = 0}, + {.name = "ry ", .color = 3, .track = 2, .position = 0}, + {.name = "saghul ", .color = 4, .track = 3, .position = 0}, + {.name = "indutny ", .color = 5, .track = 4, .position = 0}}; + +static void load_color_palette() +{ + if (!has_colors()) + return; use_default_colors(); start_color(); @@ -43,7 +43,8 @@ static void load_color_palette() { init_pair(5, COLOR_YELLOW, -1); } -static void init_screen() { +static void init_screen() +{ initscr(); curs_set(FALSE); raw(); @@ -52,45 +53,56 @@ static void init_screen() { load_color_palette(); } -static void horse_draw(luv_horse_t *self) { - if (!DRAW) { +static void horse_draw(luv_horse_t *self) +{ + if (!DRAW) + { log_info("Horse %s\ttrack: %d\t position: %d", self->name, self->track, self->position); return; } int i; attrset(COLOR_PAIR(self->color)); - for (i = 0; i < HORSE_HEIGHT; i++) { + for (i = 0; i < HORSE_HEIGHT; i++) + { mvprintw((self->track * HORSE_HEIGHT) + i, self->position, horse_pic[i]); } refresh(); } -static void add_player(uv_loop_t* loop, luv_player_t* player) { +static void add_player(uv_loop_t *loop, luv_player_t *player) +{ luv_horse_t *horse = horses + player->track; horse->position = 0; player->horse = horse; player->speed = 0; log_info("Queued horse %s on track: %d", horse->name, horse->track); - if (DRAW) horse_draw(player->horse); + if (DRAW) + horse_draw(player->horse); } -static void update_player(int rand_num, luv_player_t* player) { +static void update_player(int rand_num, luv_player_t *player) +{ luv_horse_t *horse = player->horse; - if (rand_num > player->speed) return; + if (rand_num > player->speed) + return; horse->position++; log_info("Horse %s progresses to position %d.", horse->name, horse->position); - if (DRAW) horse_draw(player->horse); + if (DRAW) + horse_draw(player->horse); } -void track_handler(uv_idle_t* handle) { +void track_handler(uv_idle_t *handle) +{ int i; luv_game_t *game = handle->data; - if (!game->in_progress) return; - if (--(game->delay) > 0) return; + if (!game->in_progress) + return; + if (--(game->delay) > 0) + return; game->delay = DELAY; @@ -100,12 +112,13 @@ void track_handler(uv_idle_t* handle) { int rand_num = (rand() % MAX_SPEED) + 1; for (i = 0; i < server->num_clients; i++) update_player(rand_num, clients[i]->data); - } -void track_init(uv_loop_t* loop, luv_client_t** clients, int num_players) { +void track_init(uv_loop_t *loop, luv_client_t **clients, int num_players) +{ int i; - if (DRAW) init_screen(); + if (DRAW) + init_screen(); for (i = 0; i < num_players; i++) add_player(loop, clients[i]->data); diff --git a/src/libuv_sandbox.c b/src/libuv_sandbox.c new file mode 100644 index 0000000..9d18df7 --- /dev/null +++ b/src/libuv_sandbox.c @@ -0,0 +1,116 @@ +#include "learnuv.h" + +static const char *filename = __MAGIC_FILE__; + +void async_hello_world_cb(uv_work_t *work_req) +{ + log_info("Hello world"); + return; +} + +void finish_cb(uv_work_t *work_req, int status) +{ + CHECK(status, "finish_cb"); + return; +} + +// int64_t counter = 0; +// Despite the unfortunate name, idle handles are run on every loop iteration, if they are active. +void wait_for_a_while(uv_idle_t *handle) +{ + handle->data++; + log_info("IDLE handler: %ld", handle->data); + + if (handle->data >= 100) + { + uv_idle_stop(handle); + } +} + +static void on_connection(uv_stream_t *server, int status) +{ + CHECK(status, "on_connection"); + + int r = 0; + + log_info("Accepting Connection"); + + // http://docs.libuv.org/en/latest/tcp.html#c.uv_tcp_init + uv_tcp_t *client = malloc(sizeof(uv_tcp_t)); + r = uv_tcp_init(server->loop, client); + CHECK(r, "uv_tcp_init"); + + // http://docs.libuv.org/en/latest/stream.html#c.uv_accept + r = uv_accept(server, (uv_stream_t *)client); + // uv_shutdown_t *shutdown_req = malloc(sizeof(uv_shutdown_t)); + // r = uv_shutdown(shutdown_req, (uv_stream_t *)client, NULL); + // CHECK(r, "uv_shutdown"); +} + +int main() +{ + setenv("UV_THREADPOOL_SIZE", "0", 1); + + int r; + // TODO: Call a timer, before handlers + + // https://docs.libuv.org/en/latest/threadpool.html + uv_work_t *work_req = malloc(sizeof(uv_work_t)); + + uv_idle_t idler; + idler.data = 0; + uv_idle_init(uv_default_loop(), &idler); + uv_idle_start(&idler, wait_for_a_while); + + // TODO: Differentiate IDLE (is it uv_idle_t?) and PREPARE handlers | + // TODO: Find out that I/O (try file reading, or networking (it's even better because we could initiate event via terminal)) is called before CLOSE handler + + // uv_fs_t *open_req = malloc(sizeof(uv_fs_t)); + // r = uv_fs_open(uv_default_loop(), open_req, filename, O_RDONLY, S_IRUSR, NULL); + // CHECK(r, "uv_fs_open"); + // char buf[20 + 1]; + // memset(buf, 0, sizeof(buf)); + // uv_buf_t iov = uv_buf_init(buf, 20); + // uv_fs_t read_req; + // r = uv_fs_read(uv_default_loop(), &read_req, open_req->result, &iov, 1, 0, NULL); + + uv_tcp_t server; + uv_tcp_init(uv_default_loop(), &server); + + /* + * Use `nc localhost 7001` to test your server (finish via Ctrl-D) and/or stop the server by sending QUIT. + nc localhost 7001 + echo 'Hello, Netcat!' | nc localhost 7001 + + * You can also send entire files, i.e. `cat package.json | netcat localhost 7001`. + cat package.json | netcat localhost 7001 + */ + char *addr = "0.0.0.0"; + int port = 7001; + uv_ip4_addr("0.0.0.0", port, &addr); + + uv_tcp_bind(&server, (const struct sockaddr *)&addr, 0); + r = uv_listen((uv_stream_t *)&server, SOMAXCONN, on_connection); + if (r) + { + fprintf(stderr, "Listen error %s\n", uv_strerror(r)); + return 1; + } + + // TODO: Understand once CLOSE handler is called + r = uv_queue_work(uv_default_loop(), work_req, async_hello_world_cb, NULL); + + // TODO: Identify CHECK handlers + CHECK(r, "uv_async_init"); + + // TODO: Find where loop time is updated + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + // TODO: But. Learn how multiple threads are actually working together inside one event loop + + // TODO: Just read about processes inside libuv, just to be aware + + // TODO: Try stopping event loop (and see how events are still processed before loop is stopped) + + return 0; +}