
An introduction to libuv (2016) - eatonphil
https://nikhilm.github.io/uvbook/
======
saghul
We merged this guide into
[http://docs.libuv.org/en/v1.x/guide.html](http://docs.libuv.org/en/v1.x/guide.html)
which got some updates to match API changes.

------
nosefrog
I work with Nikhil. He's incredibly smart and a fantastic programmer. Didn't
know he wrote a book on libuv though!

------
tapirl
> This book and the code is based on libuv version v1.3.0.

libuv v1.30 was release at on Jan 28, 2015. It is at v1.22.0.

~~~
nsm
Original author here. Yes this book was written a long time ago when I was
into the node ecosystem.

A lot of the book was copied into the official docs later
[https://github.com/libuv/libuv/pull/1246](https://github.com/libuv/libuv/pull/1246).

The book definitely does not capture new things. I do not intend to update it,
but I still look at pull requests.

~~~
saghul
I merged it into the official docs, thanks a lot for all your work nsm! <3

------
ridiculous_fish
libuv supports child processes via fork, such as in uv_spawn. It also uses
multiple threads to support uv_fs_stat, etc.

How does it handle the well known incompatibility between fork and threads?

~~~
oconnor663
Which incompatibility do you mean? If you're forking to create a child
process, usually you're just careful not to do anything that might acquire a
lock (including allocating memory) in between the fork and the exec, and then
I think you're good?

Forking for something _else_ , though, god help you.

~~~
ridiculous_fish
The tricky part of this approach is error handling. What if a system call
fails in between fork and exec, or exec itself?

It looks like libuv creates a pipe for every new process, and uses that to
send errno back on any failing system call. This has the disadvantage that you
only get the error code, and none of the context. You get ENOENT, but you
don't know which path was invalid.

~~~
geofft
They could just write the entire error string to an fd, right? It's annoying
in C to write a formatted string without doing dynamic allocations, but it's
possible.

(Or, if you want to be weird, do a PTRACE_TRACEME after fork and have the
parent trace the child process and only detach when it sees a successful
return from execve. If ptrace is unavailable, fall back to less-useful errors)

~~~
ridiculous_fish
Piping back the error string is probably the best one can do.

------
Keyframe
Is libuv used anywhere else outside of node?

~~~
snarfy
I'm pretty sure aspnetcore uses it.

~~~
sasmithjr
The default for Kestrel has been moved off of libuv, but you can choose to use
libuv if you'd like.

[https://blogs.msdn.microsoft.com/webdev/2018/04/12/asp-
net-c...](https://blogs.msdn.microsoft.com/webdev/2018/04/12/asp-net-
core-2-1-0-preview2-improvements-to-the-kestrel-http-server/)

------
newnewpdro
Why is the hello world example [0] unnecessarily allocating and freeing the
uv_loop_t? When I see this kind of crap right out of the gate I immediately
begin suspecting this is probably a pile of awful code written by a novice C
programmer then documented and published as if it's the best thing since
sliced bread.

Much of the value in making a struct (and hence its size) public and supplying
a pointer to the initializer is the potential to embed or avoid heap
allocation of the thing altogether, and here in an example which would benefit
from both the simplified code in addition to demonstrating the advantage, it's
completely missed.

Fixed form:

    
    
      int main() {
        uv_loop_t loop;
        
        uv_loop_init(&loop);
    
        printf("Now quitting.\n");
        uv_run(&loop, UV_RUN_DEFAULT);
    
        uv_loop_close(&loop);
    
        return 0;
      }
    

[0] [https://nikhilm.github.io/uvbook/basics.html#hello-
world](https://nikhilm.github.io/uvbook/basics.html#hello-world)

~~~
CJefferson
Generally, you only create one uv_loop_t, and it is vital it is never copied
(which is easy to do accisentally if you stack allocate it).

mallocing is the entirely sensible thing to do here.

~~~
newnewpdro
Then uv_loop_t should be an opaque type so its size isn't known at all, and
uv_loop_init() should instead be uv_loop_new() returning the heap-allocated
uv_loop_t, and uv_loop_close() renamed to uv_loop_free().

As-is the API and the way its use is being demonstrated in that example appear
amateur to say the least.

~~~
megous
Funny how most structs in the linux kernel are public, they must be all
amateurs, too. Perhaps go read about container_of and struct embedding in C
before calling anyone an amateur.

It's just a different programming approach in C.

Encapsulation is good only for making it harder for anyone to poke in the
internals of your library, which has some benefits with binary compatibility,
etc.

But it's strictly worse in all other metrics when coding in C. It limits you,
it forces malloc calls where most of the time none would be necessary, it
makes memory management more complicated, it forces explict initialization,
etc.

~~~
dap
If one already accepts that the object should be dynamically allocated because
otherwise it's too easy to accidentally copy (which seems specious, but that
was the suggestion), then all of those arguments apply to that recommendation,
too. And if it's "vital" that the object not be copied, then the API could
enforce that with an opaque type.

I've only worked with libuv in a few contexts, and maybe there are compelling
reasons for exposing the struct. (I'd like to hear them! I would not have
expected a uv_loop to be allocated in perf-critical code paths or code paths
where failure isn't an option.) But I think critical analysis of C API design
is an important topic. C gets a bad rap for being unsafe, which it obviously
is in many ways, but as C developers, simple choices like this (that a novice
might not even think much about) can make an API much safer -- or much less
safe.

~~~
megous
Performance is orthogonal to this. To me the less you juggle with pointers and
malloc/free the safer your code will be from memory leaks, misuse of
unallocated memory, NULL checking issues, and easier to inspect/reason about.

Struct embedding helps with this quite a bit. And it's not possible without
exposing the struct definition.

Performance gains are possible, but that's secondary.

> If one already accepts that the object should be dynamically allocated

It can still be allocated on heap, but in one continuous chunk of memory as a
part of the larger struct. Hiding the struct definition would prevent this.

~~~
dap
> Performance is orthogonal to this.

That's largely true, but it's often cited as a reason to avoiding malloc/free
(however dubious that is).

> To me the less you juggle with pointers and malloc/free the safer your code
> will be from memory leaks, misuse of unallocated memory, NULL checking
> issues, and easier to inspect/reason about.

I don't quite agree. I've worked mostly in code bases using the pattern
described earlier (an opaque pointer, a $type_create(), and a $type_destroy()
function). With that pattern, I find it much easier to be certain by code
inspection that a particular object or transformation is valid because as long
as the pointer was allocated correctly, the object can only be modified by
functions that know the type (aside from memory corruption, but that's always
possible). That's usually a small set of functions that know the struct
details. This fact is useful both as a library author and as the author of a
library consumer. By contrast, if the struct is exposed, it's harder to
identify all the places that can modify the structure's details and to be sure
that invariants are maintained in all those places.

Besides that, several other failure modes are much less likely with opaque
structures, including copying a structure you shouldn't, miscopying a
structure that's okay to copy (e.g., off-by-one while copying), or operating
on a correctly-sized block of memory that's never been initialized. You can
still use unallocated memory, of course, but that's fairly easy to make safer
by initializing pointers to NULL. (The analogous option for stack-initialized
structs -- initializing them to zero -- is not necessarily any safer than
leaving them uninitialized -- particularly if the struct contains file
descriptors.)

There are tradeoffs to both approaches. To me, the ability to modify the
struct in new versions of the library without breaking the ABI is a pretty
major point in favor of using an opaque structure for a library's primary
handle. For very simple ancillary structures that are very unlikely to change,
and where the convenience of stack allocation is worthwhile, exposing the
structure makes a lot of sense.

