mozilla/rust — pull request #1878 by olsonjeffery (2012-02-22 00:07:14+00:00)
Title: new libuv bindings

Included in this pull request are bindings for uv_async_* calls and some of the uv_timer_* calls (init, start and stop).

First, some samples (from the tests.. the code is in std::uvtmp::uv):

let test_loop = uv::loop_new();
let exit_port = comm::port::();
let exit_chan = comm::chan::(exit_port);
uv::async_init(test_loop, {|new_async|
uv::close(new_async) {||
comm::send(exit_chan, true);
};
}, {|new_async|
uv::async_send(new_async);
});
uv::run(test_loop);
assert comm::recv(exit_port);

and again with a uv_timer:

let test_loop = uv::loop_new();
let exit_port = comm::port::();
let exit_chan = comm::chan::(exit_port);
uv::timer_init(test_loop) {|new_timer|
uv::timer_start(new_timer, 1u32, 0u32) {|started_timer|
uv::timer_stop(started_timer) {|stopped_timer|
uv::close(stopped_timer) {||
comm::send(exit_chan, true);
};
};
};
};
uv::run(test_loop);
assert comm::recv(exit_port);

Obviously, a *lot* of the API and handle types are missing but the core pattern is in place for adding new handles.

## How it works, briefly:

### From the user's perspective:

user calls uv::loop_new() and gets a uv_loop in return. They use the uv_loop for operations to *create* new handles (uv_async, uv_timer, etc). The functions to create the new handle take callbacks that are invoked asynchronously after the handle is added to the loop. The user can then use the new uv_handle as they please to do various operations again libuv. It's all async, and all thread safe. So: creating new handles is async.

They can, at their leisure, start the libuv event loop by calling uv::run() or uv::run_in_bg() (the former is blocking like its C counterpart, while the latter is not). uv::run() will return when the last handle associated with it is unref'd via uv::close(). We don't really have a need for a uv::loop_delete().. the loop is cleaned up after uv_run() returns and probably shouldn't be used again.

### Under the hood:

When the user calls uv::loop_new(), we spin up a new scheduler that blocks on incoming messages, within a while loop, on a port that it creates. It sends the channel back out to the invoker (who returns it to the user as a uv_loop). We also register a (unref'd, so it doesn't affect loop lifetime) async handle whose job is buffer incoming requests, from the user, for processing by libuv. All method calls from the user (with the exception of uv::run() and uv::async_send()) are buffered through this async handle (called the op_handle).

When the user calls uv::run(), we create a new, single-threaded, scheduler that contains the libuv loop and call uv_run() there. Any operations that the user did on the loop before running will be processed at this time.

Eventually, the last handle is unref'd and uv_run() returns. We clean up the op_handle and then notify the rust loop to exit.

## What's missing

1. Most other handles that make libuv useful.
1. doesn't really hew to the core vision of working with C fns/structs directly from rust (I didn't map the uv_handle structs as brson did in the c->rust email). This would be a pretty clean refactor, IMO, would do away with a level of callbacks. Cleanly mapping out the structs to avoid landmines will be interesting, though.
2. Where will this lib live, in the end? I understand that there's a desire to keep the "thin" uv bindings around, as they're directly usable now that crust is in place. So what about these ones?
3. probably some spit and polish on the c/c++ front. it looks like the kernel->malloc()s and free()s add up, but that's about it. I'm pretty blah about the handle_data struct, too (especially once we start mapping handles that have trickier callback/data requirements). In general, cleanly using/abusing the void* data in uv_*_t structs will be interesting, especially when managed primarily from rust.
4. I'm pretty blah about how a bunch of the uv::foo() methods are, probably, going to take what i called an "after_cb", to be processed when it's done. We're basically node.js, now. This is the effect of having it be async-all-the-way-down. I did play with using chans to "send out" the handle once its created, but then the caller is just blocking while waiting for it, which felt like it was purpose-defeating, to me. Maybe if we had promises (do we?)?

I hope this change passes muster. Thanks for reading!