[Bug] Nested `Future::OnCompletion` appears to deadlock

Open
#1,621 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
cpp, firebase
Domain
api, backend

Research direction

Start with the linked reproduction project and trace the CallAsync, OnCallCompleted, FooAsync, and WaitForCompletion entry points. Reproduce the nested Future::OnCompletion case on Mac with Firebase C++ SDK 12.1.0, then compare it with the successful non-nested calls. Done means the nested FooAsync callback completes and prints the expected value instead of hanging.

Written by the indexing model from the issue text.

Description

new type: question
[REQUIRED] Please fill in the following fields:
  • Pre-built Firebase C++ SDK version: 12.1.0
  • Problematic Firebase Component: Future/Functions
  • Platform you are using the C++ SDK on: Mac
  • Platform you are targeting: Desktop
[REQUIRED] Please describe the issue here:

As the title says, nested Future::OnCompletion appears to deadlock.

I have created a sample project reproducing this problem at:
https://github.com/DistroAV/firebase-functions-future-async-nested

I have a very simple firebase function running in an emulator (the same problem happens when published to project too):

exports.foo = onCall((data, context) => {
        return 200;
    });

I then have C++ code to call this:

firebase::Future<firebase::functions::HttpsCallableResult> CallAsync(const char *name, const firebase::Variant &data,
        std::function<void(const firebase::Variant &data, int error,
                   const std::string &)>
            completion_callback)
{
    cout << "tid=" << pthread_self() << " +Cloud::Call(`" << name << "`, ...)" << endl;
    firebase::Future<firebase::functions::HttpsCallableResult> future;
    if (functions_) {
        auto httpcallref = functions_->GetHttpsCallable(name);
        cout << "tid=" << pthread_self() << " Call: `" << name << "` +Call(...)" << endl;
        future = httpcallref.Call(data);
        cout << "tid=" << pthread_self() << " Call: `" << name << "` -Call(...)" << endl;
        future.OnCompletion(
            [completion_callback](
                const firebase::Future<
                    firebase::functions::
                        HttpsCallableResult>
                    &future) {
                OnCallCompleted(future,
                        completion_callback);
            });
    }
    cout << "tid=" << pthread_self() << " -Cloud::Call(`" << name << "`, ...)" << endl;
    return future;
}

firebase::Variant OnCallCompleted(
    const firebase::Future<firebase::functions::HttpsCallableResult> &future,
    std::function<void(const firebase::Variant &data, int error,
               const std::string &)>
        completion_callback)
{
    cout << "tid=" << pthread_self() << " +OnCallCompleted(...)" << endl;
    firebase::Variant data;
    auto status = future.status();
    cout << "tid=" << pthread_self() << " OnCallCompleted: status=" << status << endl;
    if (status == firebase::kFutureStatusComplete) {
        auto httpcallresult = future.result();
        auto error = future.error();
        auto error_message = future.error_message();
        if (error == 0) {
            data = httpcallresult->data();
        }
        if (completion_callback) {
            completion_callback(data, error, error_message);
        }
    }
    cout << "tid=" << pthread_self() << " -OnCallCompleted(...)" << endl;
    return data;
}

firebase::Future<firebase::functions::HttpsCallableResult> FooAsync(std::function<void(int)> completion_callback)
{
    cout << "tid=" << pthread_self() << " +FooAsync(...)" << endl;
    auto future = CallAsync("foo", firebase::Variant(),
         [completion_callback](const firebase::Variant &data,
                     int error, const std::string &) {
             int value = (error == 0) ? data.int64_value() : -1;
             if (completion_callback) {
                 completion_callback(value);
             }
         });
    cout << "tid=" << pthread_self() << " -FooAsync(...)" << endl;
    return future;
}

int main() {
    ...
    future = FooAsync([](int value) {
        cout << "tid=" << pthread_self() << " FooAsync #3: value=" << value << endl;
        FooAsync([](int value) {
            cout << "tid=" << pthread_self() << " FooAsync #4: value=" << value << endl;
        });
    });
    WaitForCompletion(future);
    return 0;
}

When I run the code I get the following:

% ./FirebaseExample 
WARNING: Database URL not set in the Firebase config.
DEBUG: Creating Firebase App __FIRAPP_DEFAULT for Firebase C++ 12.1.0
DEBUG: Validating semaphore creation.
DEBUG: Added app name=__FIRAPP_DEFAULT: options, api_key=..., app_id=..., database_url=, messaging_sender_id=..., storage_bucket=..., project_id=... (0x...)
Using Functions Emulator at 127.0.0.1:5001
...
tid=0x1fefccc00 +FooAsync(...)
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
tid=0x1fefccc00 Call: `foo` +Call(...)
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
data: {"data":null}
tid=0x1fefccc00 Call: `foo` -Call(...)
DEBUG: Cloud Function response body = {"result":200}
tid=0x16b7fb000 +OnCallCompleted(...)
tid=0x16b7fb000 FooAsync #3: value=200
tid=0x16b7fb000 +FooAsync(...)
tid=0x16b7fb000 +Cloud::Call(`foo`, ...)
^C

The app appears to deadlock and never complete the future and print the expected FooAsync #4: value=200.
I have to Ctrl-C to end the app.

The above is simplified code for this Issue.
In my actual code I also show initializing the project app and successful multiple non-async and non-nested async calls.

Steps to reproduce:

100% repro with the code/steps listed above

Relevant Code:

https://github.com/DistroAV/firebase-functions-future-async-nested

Dominant language
C++
Stars
326
Forks
138
Avg merge
2d 16h
Merged PRs (30d)
3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from firebase/firebase-cpp-sdk

All issues in firebase/firebase-cpp-sdk

Similar issues

More C++ issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.