"feat": feature flag to allow blocking work in Futures#213
Conversation
|
Seems like it might be worth adding as an option with appropriate cautions. The One thing to note is that just because you're not using a "real" async executor doesn't make it more safe to block in async code. You can still run multiple futures concurrently with
How are you handling time along with this? Also have a fake async timer future that blocks too? |
Agreed, which is why I intended to allow
While I was drafting this PR I considered having
And I do absolutely see the value in that, but the mindset I'm coming from is: "I need to make a simple tool to do a simple job, more code == more complexity == more points of spurious failure." Serving a
To me it's less the To a provide a concrete example:
So if I want to be able to have a friendly API that can connect and do a little work, then my hands are tied between Thus why I came to the combinators idea, I was flattening these (indeed, sequential) MaybeFutures to be invoked with a single Thus, this PR to try to get the best of both worlds, just maybe. EDIT: Another example that brings me pain, is if I want to return an Error instead of the actual
Apologies, I threw in the mention of that older project since I had many of these same questions then too, but at that point I had decided to go fully sync to keep my sanity intact as I didn't want to find/make a As such, my I should note that there is a separate I hope this makes my intentions a little more clear. If I'm missing something or this is just plain out of scope, let me know. I came to |
|
So I got that If you have thread 'main' (573005) panicked at .../src/maybe_future.rs:106:18: there is no reactor running, must be called from the context of a Tokio 1.x runtime So while this PR does work for allowing any executor to consume my So I'll have to rethink my library's approach, most likely. Though I do have to ask, in an ideal usage of this library, what does a good device wrapper look like? As I've mentioned previously, returning early with an And I'm unclear about doing transfers (i.e. getting status via N number of Control INs) before returning the Wrapper. Simply wrapping it in a parent function doesn't seem enough, since it also gets colored by the |
I did read this originally, but only after the last two weeks do I actually understand the pain 😅
Being
asyncruntime or evenasync/sync-agnostic is something I've desired in the past, today for my current project, and likely will in the future as well.Even something as "simple" as sleeping for a short period of time is an issue, which I get! Sometimes you can keep it all in userspace, sometimes you're putting a microcontroller core to sleep until an interrupt hits, it's not as trivial to have a universal Sleep future... which is an absolute pain when the protocol documentation I'm dealing with says "wait 1/16th of a second between poll attempts" and now I'm pulling in DelayNs from the
embedded-halcrate on my PC builds so that I can have a shared abstraction between it and my embedded builds and put it as a supertrait to a trait that also has all the SPI bus stuff and-Anyway, X/Y problem that I've been working on is: I need to reliably poke a USB device with Ctrl and Bulk endpoints, and potentially serve a
cdylibfor Windows and Android. Ideally, I could freely drop intokioor whatever when being directly included as a dependency, but also be able to run the whole thing synchronously when I'm compiling the dylib frontend, to minimize potential points of failure in an already-fragile context.I started experimenting with combinators for
MaybeFuture, starting with stuff likemap_okandmap_err. I ended up getting really far with ones based offfutures-rs's combinators, resulting in the creations of gems likeEitherMaybeFuture, eventually(Try)FlattenMaybeFutureand sketch-out for aTryFold/Collect/ForEachMaybeFuturebefore realizing that I am just reinventingasync/awaitwith stone knives and bearskins. It took a singleforloop in my actual application to break my will to continue there. Restructuring a couple functions to useasync, it flows far more naturally while also being shorter to express... At least I learned a lot aboutasync's internals and how toexpress stuff with traits/type boundswrestle the trait solver into submission.After staring at the recently-posted #212 and my own runtime panics while benchmarking
pollster, here I am with this two-birds-one-stone "idea"; optionally allow running theBlockingTaskclosures directly, and changing the runtime.awaitpanic into a compile-time error asking to choose one of the three possibilities. If any of the actual runtimes are enabled whileblocking-in-asyncis enabled, they're preferred in the same order they were originally (smol>tokio>blocking-in-async>compile_error!).I know this probably wouldn't fit well with any isochronous design, and I'm unaware if any of the
.wait()vsIntoFutureimplementations are significantly different enough to cause later issues, but it seems to work well enough in my naive tests. I'd love to hear if you've considered this or other ideas before.I'm also considering also sending a PR to
pollsteritself, since theirFutureExtis only blanket-impl'd forT: Future, notT: IntoFutureeven though the actualfnis generic overT: IntoFuture. This boils down to invokingMaybeFutures withx.into_future().block_on()orblock_on(x)(unless I choose to just make my entire API surfaceasyncand not have a single-> impl MaybeFuturepublic for consistency's sake).Part of my intent with this PR and associated ramblings is also just me asking, "am I missing something?" Is there an easier way to be mostly agnostic with your core functionality of poking a known protocol, of which is both simple yet complex enough to be a genuine challenge to maintain a sync and
asyncvariant of? Am I just making things more complicated than they need to be? :P