-
Notifications
You must be signed in to change notification settings - Fork 0
Add future module, release concurrency permit in vacation-spawned task, readme/test improvements, remove default features
#11
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
Conversation
…ther than foreground future dropping)
…o (quicker) test runtime handle
|
Just want to call out, the usage of My previous impl was to split apart the tokio-rustls feature and send the full session into the vacation work. I technically could still do that, more concisely, since i'll have the mutable pointer to the inner future. So I could still add a new state transition to the inner future that just returns pending until vacation work is complete. I'd prefer to avoid that, though, we'll see. I suspect it's going to be a tradeoff between adding complexity to the tokio-rustls future, or trying to tweak rustls to play nicer with this sort of usage. Anyway I think probably this is the right API for vacaiton, since it both provides flexibility to do something like this. |
…, various doc comment updates
|
Updated it based on offline discussion:
The examples are still somewhat contrived... I'm going to construct a richer example when I go to integrate this with tokio-rustls, which I can backport here. Between the integ and unit tests, though, I think the API is fairly clear. The part that I like the least is that now we require the caller to call If we'd prefer to go back to the caller specifying synchronous work closures and implicitly executing, we could, but I agree that that's a bit unintuitive. The other note is, I do think I want to add namespacing in the execute call (ref #3 (comment)). But, I'll do that in a subsequent PR to avoid overloading this one. I like that idea a lot though, since it allows quite granular strategy selection (including selective prioritization via custom closure), without a lot of config drilling. Anyway, I'll try to at least have the input side of that (in |
|
Something to call out - while it might seem like we could just include the incorporator function behavior directly in the async block our I'll add that to a FAQ (along with why not just use channels). I think it might work ok on the OffloadFirst future... Do you think that is a better API? It will make the input offload work future busier, but make one less builder call? The downside is that it totally removes any vacation related bounds so maybe is more prone to misuse? At that point I also really might as well just use futures util... In which case maybe I just ditch the OffloadFirst functionality entirely since it is trivial for libraries to implement with futures util then combinator? On the fence about this, feedback welcome |
…uture>>` rather than `Box<dyn Future + Unpin>
|
Just went ahead and threw in operation namespace on the input side. Since I want to pin down the input signature before PR-ing tokio-rustls. The only thing actually using it right now is the custom closure. This will generally close out: #3 For #4 - we still will want to allow the caller to specify a mapping of strategies to namespace, presumably, rather than always using custom closures. Similar story to adding handling based on chance of blocking. That I'll keep as a separate PR. |
|
I'm actually thinking of refactoring to have the suppress inner poll mode accept an owned inner future into the get_offload_work closure, and then the caller can directly include the incorporate fn in that returned async block since it will own the inner future anyway. And then they would return the inner future olfeom the async block. This is nice because it wouldn't require shuffling around a new state machine step inside of tokio-rustls. Presumably other libraries with a similar pattern too. In general I think that api might be a bit more intuitive, so I might ditch the pass through poll inner entirely until I have a use case for it? It would be easy enough to add back in... |
|
I think I'm sold on removing the Meanwhile, the So the signature struct would be something like: Which would imply also only having suppress inner poll mode. We can layer borrowed inner futures back in, if we want, but I think it's scope creep to do so without a direct use case. The main awkward bit with this approach is, the |
|
We discussed offline, I'll make the above changes but in a separate PR to avoid continuing to bloat this one up |
The highlight here is future module, with some other small stuff.
Future module
Based on initial usage in tokio-rustls, I want to expand this package to also include 'batteries' for libraries that are manually implementing futures. Since, needing to progressively poll vacation work before or alongside inner futures, is going to be a frequent use case. And, it takes a lot of intrusive boilerplate to manually implement it.
It's all optional under feature flag, though the only new dependency is
pin-project-lite. LMK if you think I should remove the flag.Currently I am targeting two use cases:
The first case is fairly simple. That is essentially just the futures-util Then combinator, with some convenience methods in the builder to construct the vacation work. I ended up rolling my own, but I'm happy to cut over to using futures-util if there are any issues with my impl.
The flow for
VacationFirstis:The second case is a bit more complex. The constraint I'm operating in is, the inner future generally cannot be assumed to be fully composed of resources that are send + 'static. But, we do need access to certain elements of state derived from the inner future, both to generate the vacation work, and to handle the results of the vacation work.
We also would prefer to continue polling the inner future alongside the vacation future, since tokio-rustls's I/O work can sometimes proceed simultaneously. Anyway the inner future can always return
Poll::Pendingif it needs to wait on vacation.The API I went with for
VacationOngoing, is:inner_future.get_buffer_to_process_with_vacation().A couple notes:
Boxthings in theVacationFirstresolver closure orVacationAlongsideget_work closure. I could do it for them, but often they need to return futures that already are sized and unpin, since they have defined them lower down. So it would have forced extra allocations to do it implicitly. Open to alternatives.Concurrency handling changes
I noticed an edge case in concurrency handling, where the calling future might drop (which holds the vacation executor handle to the sync work). The concurrency permit was acquired and dropped in the foreground, so concurrency would be released.
However, our vacation work isn't actually cancellable since we cut over to sync API, since it doesn't yield anywhere to drop work. So, the work could keep running after concurrency permit dropped, resulting in going beyond the limit.
Moving the permit drop to after the vacation work finishes, instead.
Remove default features
The tokio default feature no longer seems sensible given that we don't implicitly update default strategies anyway. Just forces libraries to add
no-default-features = true. Removing the implicit tokio default.Test improvements
My concurrency tests were occasionally failing, and I realized why - I was racing initializing the strategy against multiple test cases per integ test binary. Which, for spawn_blocking and some custom executor tests, implicitly is loading a specific runtime handle to use with the strategy.
And, that runtime ends when the test ends. Which, if it's not the concurrency test, will generally finish quicker than the concurreny test. This results in the listener channel closing and the vacation work getting cancelled, from the caller's perspective, and thus exceeds concurrency limit.
I also added an assertion that all vacation work actually succeeded, I was silently ignoring the cancellation related errors.
Avoid async fn in trait
We had a trait definition for
execute()that had a async function in it. Given that tokio-rustls supports 1.70, and AFIT is 1.75, I'd rather avoid forcing a MSRV bump to use vacation. We don't actually need the trait for anything since we are using enum dispatch anyway. Refactored to remove.Add operation namespace to
vacation::execute()Updated the
vacation::execute()input to include an operation namespace, that is just a &'static str. This will open up a world of customizability without adding config drilling into the crate.I focused on how it impacts the input end (as well as custom closure, since that was easy). I'll add richer support for customizing strategy based on this operation namespace (and chance of blocking), later.