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

API question: Is there value_or(...) equivalent? #49

Open
mkucmpro opened this issue Sep 13, 2021 · 7 comments
Open

API question: Is there value_or(...) equivalent? #49

mkucmpro opened this issue Sep 13, 2021 · 7 comments

Comments

@mkucmpro
Copy link

mkucmpro commented Sep 13, 2021

I wanted to know if there exists an API similar to value_or(...) that would allow me to do something like:

struct Something { int interesting{0}; };

int getter() {
    QtPromise::QPromise<Something> promise = ...;  // Obtained somehow

    return promise.wait().value_or(Something{}).interesting;
}

I often find myself doing something similar to:

int getter() {
    QtPromise::QPromise<Something> promise = ...;

    int outerInteresting = 0;
    promise.then([&](const Something& s) { outerInteresting = s.interesting; }.wait();
 
    return outerInteresting;
}

Would an addition like value_or() be useful for the library?

Btw. I was also thinking that to_optional() could also fit my needs but that would require C++17 (and AFAIK the library aims to support C++11). What is the authors' current attitude towards conditional compilation and feature testing (if the compiler supports C++17)?

@pwuertz
Copy link
Contributor

pwuertz commented Sep 15, 2021

You are trying to retrieve an asynchronous promise result in a syncronous way. There is an example for this in the tests:

static inline T waitForValue(const QtPromise::QPromise<T>& promise, const T& initial)

But generally, this is not what promises are made for. I'd even go as far as saying that using wait() in production code is dangerous, since you might end up in reentrancy situations you weren't planning for.

@mkucmpro
Copy link
Author

mkucmpro commented Sep 16, 2021

@pwuertz What I have is an async API which I need to make (ocasionally) synchronous to interoperate with the rest of the system.

To achieve this, I'd need some equivalents of sync_wait() and value_or().

The question I'm trying to ask is whether these kind of member functions would be useful to have in the API (instead of having my own free functions that would do the same).

@simonbrunel
Copy link
Owner

Would an addition like value_or() be useful for the library?

I'm not sure it would be really useful since, as @pwuertz mentioned, the .wait() API is probably not a good practice (I initially implemented it for the unit tests). Maybe there are some situations where we know for sure that the promise is resolved, thus that method would make easier retrieving the value, though I don't think of any good example.

I see that bluebird provides APIs to inspect the promise values so it may make sense to have similar methods. However, value() should throw an error if the promise is not fulfilled. The value() method could have an optional argument as fallback in case the promise is rejected, which would be a shorthand for promise.fail(()[] { return -1; }).value() (written promise.value(-1)).

I was also thinking that to_optional() could also fit my needs

I don't get how an optional would make things better (you would still need to call .wait(), right?). Can you provide an example?

@pwuertz
Copy link
Contributor

pwuertz commented Sep 30, 2021

Maybe there are some situations where we know for sure that the promise is resolved, thus that method would make easier retrieving the value, though I don't think of any good example.

There is indeed one good example I came across recently. I've been playing around with C++ co-routines and implemented a co_await for QPromise objects.

The spec defines a await_ready() method which allows you to skip the suspension of a co-routine if you know that the awaitable is done, i.e. if QPromise is not pending. In this situation you'd require sync access to the QPromise result value (or exception) for implementing the await_resume() method.

... value() should throw an error if the promise is not fulfilled.

Indeed this is also what python does with asyncio futures. If a promise/future is fulfilled or rejected, you can collect the result or the exception synchronously with future.result() and future.exception(). Doing this with a pending future fails with an InvalidStateError exception.

@mkucmpro
Copy link
Author

@pwuertz @simonbrunel Could you advise what would be the best way to implement a wait() equivalent that would be safe for production code?

I'm asking because I'm facing exactly these kind of reentrancy issues when calling wait(). What I want to achieve, in principle, is to have an "async API" which I could ocasionally use in a synchronous way.

I was trying to think on an answer, and the two ideas I have:

  1. introduce another thread with an event loop that would be used to "resolve/reject" the promise
  2. not use QPromise for that approach

@pwuertz
Copy link
Contributor

pwuertz commented Feb 7, 2022

Could you advise what would be the best way to implement a wait() equivalent that would be safe for production code?

Generally, there is no way to do this. Think of it like some sort of logical-fallacy. You can't (and shouldn't) block-wait on something that relies on being used in a non-blocking context.

I'm asking because I'm facing exactly these kind of reentrancy issues when calling wait(). What I want to achieve, in principle, is to have an "async API" which I could ocasionally use in a synchronous way.

Asynchronous programming is extremely 'viral'. The only viable way to do this is by refactoring the synchronous code, e.g. by making it async or by using classic continuation patterns like callbacks.

introduce another thread with an event loop that would be used to "resolve/reject" the promise

This may work in specific situations, but can't work in general. In Qt for example there is only one GUI/main thread. If you block this one, any promise waiting for a signal from there won't ever resolve. In order to do this safely, you as a developer require exact knowledge about which promise is safe to be-sync-blocked at which line using which thread.

  1. not use QPromise for that approach

This is a general design issue which isn't specific to QPromise. As a reference, you can't sync block Python Async-Coros or JavaScript async functions either.

@pwuertz
Copy link
Contributor

pwuertz commented Feb 7, 2022

@mkucmpro
"ocasionally use in a synchronous way" might work in specific situations if you're able to control and contain the event emitter in a thread. For example, you can wrap an asynchronous 'download-this-file'-function in a sync function by creating a socket and a worker thread, then wait for the completion of the transfer synchronously in the calling thread. I'd consider this to be "safe" since the user of this method can't mix up thread affinity of events and handlers, or inject anything that might run in an unexpected context.
So the async function / qpromise object would be contained in the worker thread loop, and the sync function would communicate the result as QFuture for example.
This pattern requires that you create a dedicated thread for each io-bound async task, which is something async programming tries to eliminate.

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

No branches or pull requests

3 participants