Skip to content

DistroAV/firebase-functions-future-async-nested

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

firebase-functions-future-async-nested

This repo is an example/test app to repro a problem I am seeing using https://github.com/firebase/firebase-cpp-sdk where firebase::Future<firebase::functions::HttpsCallableResult> appears to deadlock when nested inside a Future::OnCompletion.

The problem happens on both the Emulator and the live project.

Opened firebase-cpp-sdk Issue: firebase/firebase-cpp-sdk#1621

Example...

Works:

    auto value = Foo();
    cout << "tid=" << pthread_self() << " Foo #1: value=" << value << endl;
    value = Foo();
    cout << "tid=" << pthread_self() << " Foo #2: value=" << value << endl;
    auto future = FooAsync([](int value) {
        cout << "tid=" << pthread_self() << " FooAsync #1: value=" << value << endl;
    });
    future = FooAsync([](int value) {
        cout << "tid=" << pthread_self() << " FooAsync #2: value=" << value << endl;
    });

Deadlocks:

    future = FooAsync([](int value) {
        cout << "tid=" << pthread_self() << " FooAsync #3: value=" << value << endl;
        FooAsync([](int value) { // <- deadlocks a few levels into this call and firebase never logs it called the firebase function
            cout << "tid=" << pthread_self() << " FooAsync #4: value=" << value << endl;
        });
    });

Log of the above 6 calls:

% ./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 +Foo(...)
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(...)
tid=0x1fefccc00 ProcessEvents(100)
DEBUG: Cloud Function response body = {"result":200}
tid=0x1fefccc00 +OnCallCompleted(...)
tid=0x1fefccc00 -OnCallCompleted(...)
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
tid=0x1fefccc00 -Foo(...)
tid=0x1fefccc00 Foo #1: value=200
tid=0x1fefccc00 +Foo(...)
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(...)
tid=0x1fefccc00 ProcessEvents(100)
DEBUG: Cloud Function response body = {"result":200}
tid=0x1fefccc00 +OnCallCompleted(...)
tid=0x1fefccc00 -OnCallCompleted(...)
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
tid=0x1fefccc00 -Foo(...)
tid=0x1fefccc00 Foo #2: value=200
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 #1: value=200
tid=0x16b7fb000 -OnCallCompleted(...)
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
tid=0x1fefccc00 -FooAsync(...)
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 #2: value=200
tid=0x16b7fb000 -OnCallCompleted(...)
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
tid=0x1fefccc00 -FooAsync(...)
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

Note at the end that the app hung indefinitely before the 6th DEBUG: Calling Cloud Function was logged and I had to Ctrl-Break.

I suspect this has something to do with the muxtex(s)/semaphore(s) used to track pending Futures and their OnCompletion.

One admittedly not directly related hint I got about this was from firebase/firebase-cpp-sdk#51 (comment)

In my case I resolved the issue by ensuring the HttpsCallableReference was not destroyed until the future had resolved. The destructor for this class releases the future and calls rest::CleanupTransportCurl() which I imagine is the cause of the problem.

It seems like the official code could stand for a bit more verbose logging around these areas... but what do I know?

I am not certain if these issues are [closely?] related:

To test/run/use...

firebase functions

cd firebase

To run in emulator:

firebase emulators:start --only functions

To deploy to firebase cloud:

firebase deploy --only functions

app

To build:

cd app

# once, or whenever firebase-cpp-sdk changes
pushd libs
./get-firebase-cpp-sdk.sh
popd

mkdir -p build && cd $_

# Define an Android app in your firebase project and download its google-services.json
cp /path/to/your/google-services.json .

# once, or whenever CMakeLists.txt changes
cmake ..

# whenever code changes
cmake --build .

To run [and use firebase emulator by default]:

./FirebaseExample

To run and not use emulator [use firebase cloud]:

./FirebaseExample --no-emulator

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published