Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std::uv::global_loop and std::timer #2261

Closed
wants to merge 25 commits into from
Closed

std::uv::global_loop and std::timer #2261

wants to merge 25 commits into from

Conversation

olsonjeffery
Copy link
Contributor

At a high-level, this pull request contains:

  • fleshing out the std::uv::hl module (provides stuff needed to run a "high-level", rust-managed loop with hooks for user-supplied logic/environment
  • adding the std::uv::global_loop module, which provides a single, process-wide event loop leverage the types and functions built out in std::uv::hl
  • adding the std::timer module, which uses std::uv::global_loop to provide a high-level interface to the libuv uv_timer_* API.

std::uv::hl

A set of functions and types to interact safely with a libuv, primarily from rust code. The test and functions in the test module provide a pretty good overview of how to do work it (including the bare minimum to roll-your-own high_level_loop)

std::uv::global_loop

As mentioned above, this is the process-wide event loop that (presumably) stdlib developers can use to expose high-level functionality to libuv.

There are two, full implementations that are present at this time:

  • The get_single_task_gl function (exported but hidden in docs) provides a libuv loop that consumes a single task/single-threaded-scheduler. This was my original implementation and, while (ostensibly) lighter-weighter in message/task traffic, it most likely has some race conditions.
  • The get_monitor_task_gl function (also exported but hidden in docs) gives access to a libuv loop that's implemented over two tasks (each running in their own single-threaded scheduler). This is the implementation that is wired up to uv::global_loop::get(), which is the only fn from this module that appears in the docs

I want to work on the single-task global loop a bit more and, eventually, set up some profiling between the two versions (to test under load). But for now we're shipping what (should) be a race-free version.

NOTE: Do not use both of the above functions within the scope of a single-process lifetime (always use one or the other, but not both). Using just std::uv::global_loop::get should (obviously) be your first choice 99.999999% of the time.

If we're satisfied, relatively speaking, with the perf of the get_monitor_task_gl() impl I'll just drop the get_single_task_loop impl.

std::timer

Provides a few useful functions that use libuv's timer API. Currently only three exported functions:

  • timer::delayed_send - send a msg after the provided timeout
  • timer::sleep - block the task this is called in for the specified time period
  • timer::recv_timeout - block on recv for up to the specified timeout. If a msg is recv'd on the provided port before the timeout expires, we return some(T), otherwise if the timeout period passes without a msg on the provided port, we return none.

Interestingly, it seems like timer::delayed_send is the primitive upon which you can build everything else in the module, AFAIK.

I also wanted to add a more tradition timer interface that just takes a cb and calls it repeatedly in a new task until it returns false, but didn't get around to it.

The implementation, here, is the first non-infrastructure/proof-of-concept use of std::uv::*. hooray!

still unresolved

  • The management of libuv structs' lifetime, stored on the rust stack, is ackward and does not map cleanly to the C/C++ memery (of course!). So I've been trying to work on abstractions to provide some sort of safety net around this. We also have to integrate the high_level_loop's internal reference counting scheme (that sits atop the libuv refcount scheme). I think I have something in mind (a resource, stored in a shared box, that will tie the struct value to a given task.. with some automated setup/teardown), but it's not quite ironed out yet (and places its own burden on the user). For now, we have the uv::hl::ref, uv::hl::unref and uv::hl::unref_and_close fns. Any code that uses libuv structs, on the stack should make sure to:
    • ref at or before the time you make a uv_init_* call with the ptr to the struct value.
    • unref and uv_close before the task containing the struct exits. This is not always straightforward and requires some thoughtful synchronization via ports/msgs that I'm still working. The tests in hl, global_loop and the impl in std::timer should different examples/approaches for doing this safely. It's an ongoing thing.
  • Should this move into core? Probably want to let it prove itself, first.

up next

  • Iron-out remaining rough edges in the API and implementation
  • work on getting profiling set up for the loop in general, as well as specific parts of the API.
  • The low-level plumbing is in place for IPv4 TCP/IP, so I'll move towards flushing that out in a high-level API build around port/chan data passing (this work is blocked, on 32bit linux at least, pending Rust<->C by-val ABI issues on 32bit #2064)
  • Work towards high-level HTTP client and server scenarios leveraging a high-level TCP/IP API.
  • Bind more of the libuv API's surface, in general, to rust (in std::uv::ll) and write tests
  • More tests, better docs

adding two pointers fields to rust_kernel :(
.. have to do manual malloc/free for one of the fields, which feels wrong
- starting/stoping the loop based on client work is functioning, correctly
- the issue appears to be that, when the process is about to exit, the
signal to let weak tasks know that they need to exit isn't getting fired.
.. seeing an occasional valgrind/barf spew on some invalid read/writes..
need to investigate further.. i think its related to my poor citizen
conduct, re: pointers stashed in rust_kernel..
- moved global loop tests, as well.. will add tests in uv_hl that encompass
rolling your own high_level_loop via uv::hl::run_high_level_loop()
- also whitespace cleanups and misc warning cleanup..
- doesn't work on 32bit linux
.. fixes issue, in previous commit, with global loop test hanging on
32bit linux (this was because the struct was too small, so (presumably),
the data member was garbled.. yippy)
.. leveraging std::uv, we have:
timer::delayed_send - send a value over a provided channel after the
timeout has passed
timer::sleep - block the current task for the specified period

both of these fns (and everything that goes in timer.rs) leverage the
uv_timer_* API
@brson
Copy link
Contributor

brson commented Apr 20, 2012

Thanks for all this work and for providing such a thorough description in the pull request!

@brson
Copy link
Contributor

brson commented Apr 20, 2012

Merged.

@brson brson closed this Apr 20, 2012
bors added a commit to rust-lang-ci/rust that referenced this pull request Sep 22, 2022
make rustfmt mandatory and used pinned toolchain

Looks like this is what most people prefer/expect, and using a pinned toolchain for formatting avoids some (rare and so far mostly hypothetical) formatting inconsistency issues.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants