Skip to content

Add QCoro::connect helper function#125

Merged
danvratil merged 1 commit intoqcoro:mainfrom
jbruechert:feature/connect
Oct 18, 2022
Merged

Add QCoro::connect helper function#125
danvratil merged 1 commit intoqcoro:mainfrom
jbruechert:feature/connect

Conversation

@jbruechert
Copy link
Contributor

This adds a function that is a bit more similar to QObject::connect. It's main feature is that it can take a context object, and thus can be used to interface between coroutines code and synchronous code, for example in list models.

@jbruechert
Copy link
Contributor Author

@danvratil Do you know why the memory leak only occurs in certain configurations? Or is there simply no asan on apple clang and msvc?

Copy link
Collaborator

@danvratil danvratil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

thanks for the patch, it's a good feature!

I did only rough review, I'll help you with the Asan issue after the weekend when I'm back at my computer. Reading Asan reports on the phone screen is really hard 😅

@danvratil
Copy link
Collaborator

danvratil commented Oct 15, 2022

Regarding Asan, It is off on Windows right now, but should be enabled on Apple, so it's weird that apple-clang and regular clang detections would differ. I'll investigate later.

Edit: I see now that the failing matrix is more complex...hmmm

@jbruechert jbruechert force-pushed the feature/connect branch 2 times, most recently from 9eb1ed8 to b00dae4 Compare October 15, 2022 19:58
@danvratil
Copy link
Collaborator

Looks like the leaked memory is the coroutine frame - whether it leaks or not depends heavily on the coroutine code generated by the compiler, which is why the matrix is failing in this pattern.

Looks like GCC is affected in all versions, clang-11 and apple-clang (== clang-12) are fine, clang-14 and -dev are affected, likely due to some change in the codegen since clang-12.

Anyway, the real cause for the memory leak is either a bug in QCoro, or a bug in compiler, consider this testcase:

    QCoro::Task<> testTaskConnectOK_coro(QCoro::TestContext) {
        auto task = timer(1000ms);

        bool called = false;
        auto context = std::make_unique<QObject>();

        QCoro::connect(task, context.get(), [&]() {
            qDebug() << "Task done in connect()";
            called = true;
        });

        qDebug() << "Task ready?" << task.isReady();
        co_await task;
        qDebug() << "Task resumed, ready=" << task.isReady();

        QTest::qWait(2000);
    }

And the output from running this testcase:

PASS   : QCoroTaskTest::initTestCase()
QDEBUG : QCoroTaskTest::testTaskConnectOK() [13:15:41.698] test-qcorotask(139932:139932) QCoroTaskTest::testTaskConnectOK_coro: Task ready? false                                                               
QDEBUG : QCoroTaskTest::testTaskConnectOK() [13:15:41.698] test-qcorotask(139932:139932) QCoroTaskTest::testTaskConnectOK_coro: Task resumed, ready= false
QDEBUG : QCoroTaskTest::testTaskConnectOK() [13:15:42.751] test-qcorotask(139932:139932) QCoroTaskTest::testTaskConnectOK_coro: Task resumed, ready= true

Somehow, co_await task doesn't suspend, even though the task is not yet ready, however when the task finished, the test coroutine gets resumed from a suspension point that has already been crossed. (that was some serious WTF moment seeing this log output for the first time).

It could be a bug in compilers, but I before I go down that rabbit hole, I want to make sure it's not a bug in QCoro where the task is effectively being co_awaited twice: once inside the implementation of .then() used inside QCoro::connect() and once the co_await task in the top-level.

@danvratil
Copy link
Collaborator

After some assembly debugging and re-reading of the C++ standard, I found that, as I suspected, it's a QCoro bug related to the same coroutine being awaited twice: the QCoro::detail::TaskAwaiterBase::await_suspend() suspends the current coroutine and stores reference to it to promise of the coroutine being awaited (QCoro::detail::TaskPromiseBase::setAwaitingCoroutine) which returns true the first time, causing the current coroutine to be truly suspended (in our case that should be the QCoro::Task<>::thenImpl()).

When the code reaches the co_await task place, the await_suspend() for the test coroutine function is called, with reference to the task coroutine again, and calling setAwaitingCoroutine, it replaces the original themImpl() coroutine function with itself as the real awaiter, but more importantly, setAwaitingCoroutine will now return false, causing the test coroutine function to not actually suspend, but continue executing.

This is obviously a bug in QCoro, probably from back when I still had no idea what I was doing :-) I'll create a dedicated test scenario for this and let you know when it's fixed.

@danvratil
Copy link
Collaborator

Hi @jbbgameich, could you please rebase your PR on current main? The issue with multiple awaiters should be fixed (see #127 for details).

@jbruechert
Copy link
Contributor Author

Seems to work fine now, thanks for the fixes!

This adds a function that is a bit more similar to QObject::connect.
It's main feature is that it can take a context object, and thus can be
used to interface between coroutines code and synchronous code, for
example in list models.
@danvratil danvratil merged commit cbf5bbd into qcoro:main Oct 18, 2022
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