-
Notifications
You must be signed in to change notification settings - Fork 0
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
initial implementation #1
base: main
Are you sure you want to change the base?
Conversation
I don't think a long name now is a problem.
I believe you should return a A builder might be nicer to find references for. For example, one that is used like
If you don't use
This should not affect performance significantly. In any interesting case both branches should be perfectly predictable. |
…input rather than panic, add concurrency control + customizable channel size to secondary runtime
…der method naming
…s of custom executor closure input to include `Send + static`, abstract cancellation logic, change multithreaded tokio default strategy from block_in_place -> spawn_blocking, stash the default strategy in its own oncelock
@arielb1 - all comments should be addressed, thanks again for the close read The main thing I want to call out is, I ended up adding a secondary I needed this because I started having lifetime issues on the I figured it was probably fine since it still avoids the sharp edge of libraries calling Otherwise, I tried to break changes into commits, though some got a bit bigger. Here's the rundown: Changes since last review
|
… since it requires polling
Sharing initial commit for design feedback. Please pick it to pieces. Glad to split into smaller commits if it'd be helpful. I figured it was short enough that seeing the whole picture might be useful.
Some specific questions:
Crate name
Is
compute-heavy-future-executor
too long? Most crates are like 6 letters long. I was trying to make it clear what it does, but it's pretty klunky. Open to alternativesStrategy-setting API
I don't love the current ergonomics of initializing a strategy. We have all these calls like
initialize_block_in_place_strategy()
which either initialize the oncelock or panic.I was considering something like a builder or an enum, but it felt ridiculous given that there is only really one step for these and then we stash any output in the once lock. And then they are more verbose to call.
Other ideas welcome.
Custom executor
I wanted to put in an escape hatch to allow alternative async runtimes / customizing existing strategies with extra metrics or whatever / etc. But, the current form isn't particularly pleasant.
Ideally the caller would be able to implement the
ComputeHeavyFutureExecutor
trait themselves, but there were a few issues:Box<dyn ComputeHeavyFutureExecutor>
because the trait isn't object safe to its own generic bounds on itsexecute(fut: F)
methodInstead I stored a closure inside of the
CustomExecutor
struct. That closure takes a future withAny
output on the way in, and returns one withAny
output. Because ourexecute
call does have concrete typing, we can temporarily type erase the future's output before sending it into the closure, and then downcast it back on the flip side.I think it's sound (please correct me, of course). Unfortunately we only are validating that the closure we get doesn't mangle the type, on initialization, not at compile time.
In addition to complexity, this also adds quite a few extra allocations due to all the added vtables and such.
I'm open to alternative approaches. Or, if it's better just to drop this functionality from the library entirely, and force people to stay on the rails, I'm open to that too.
Using
get_or_init()
insidespawn_compute_heavy_future()
Feedback on some dummy code in rustls/tokio-rustls#94 (comment) was that
get_or_init()
was overkill top call from inside the spawn handling.As best as I can tell, using
get_or_init()
rather than matching against the runtime flavor every time this function is called, is strictly more efficient - since, it anyway starts off withOnceCell::get()
, and then only calls the secondary logic if it was uninitialized - ref https://doc.rust-lang.org/beta/src/std/sync/once_lock.rs.html#387This means that with
get_or_init()
, in case we need to use defaults, we have the single get branch every call, and the first call, the additional initialization logic.Meanwhile with just a naked
get().unwrap_or_else()
, we still have the single get branch every call, and then the additional match branch against runtime flavor.Probably a minor point either way, just calling out my reasoning so that it can be corrected as needed :)
Use of log crate
It felt strange depending on
tracing
when tokio itself is optional. I was browsing other libraries and was seeing that many of them just use the simplelog
crate. Which has interop with tracing anyway.Is that the right choice? Or are libraries just using
log
because they were written beforetracing
was widely used?Testing
Unit tests + doc tests succeed locally, with and without the tokio cfg flag enabled. I didn't want to overload this PR even further by setting some github actions, but I'll get to it!