Skip to content

Commit 4e1c66d

Browse files
committed
Initial commit
0 parents  commit 4e1c66d

14 files changed

+581
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
package-lock.json

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 DistroAV
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# firebase-functions-future-async-nested
2+
3+
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>`
4+
appears to deadlock when nested inside a `Future::OnCompletion`.
5+
6+
The problem happens on both the Emulator and the live project.
7+
8+
Opened firebase-cpp-sdk Issue: https://github.com/firebase/firebase-cpp-sdk/issues/1621
9+
10+
Example...
11+
12+
Works:
13+
```C++
14+
auto value = Foo();
15+
cout << "tid=" << pthread_self() << " Foo #1: value=" << value << endl;
16+
value = Foo();
17+
cout << "tid=" << pthread_self() << " Foo #2: value=" << value << endl;
18+
auto future = FooAsync([](int value) {
19+
cout << "tid=" << pthread_self() << " FooAsync #1: value=" << value << endl;
20+
});
21+
future = FooAsync([](int value) {
22+
cout << "tid=" << pthread_self() << " FooAsync #2: value=" << value << endl;
23+
});
24+
```
25+
26+
Deadlocks:
27+
```C++
28+
future = FooAsync([](int value) {
29+
cout << "tid=" << pthread_self() << " FooAsync #3: value=" << value << endl;
30+
FooAsync([](int value) { // <- deadlocks a few levels into this call and firebase never logs it called the firebase function
31+
cout << "tid=" << pthread_self() << " FooAsync #4: value=" << value << endl;
32+
});
33+
});
34+
```
35+
36+
Log of the above 6 calls:
37+
```bash
38+
% ./FirebaseExample
39+
WARNING: Database URL not set in the Firebase config.
40+
DEBUG: Creating Firebase App __FIRAPP_DEFAULT for Firebase C++ 12.1.0
41+
DEBUG: Validating semaphore creation.
42+
DEBUG: Added app name=__FIRAPP_DEFAULT: options, api_key=..., app_id=..., database_url=, messaging_sender_id=..., storage_bucket=..., project_id=... (0x...)
43+
Using Functions Emulator at 127.0.0.1:5001
44+
tid=0x1fefccc00 +Foo(...)
45+
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
46+
tid=0x1fefccc00 Call: `foo` +Call(...)
47+
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
48+
data: {"data":null}
49+
tid=0x1fefccc00 Call: `foo` -Call(...)
50+
tid=0x1fefccc00 ProcessEvents(100)
51+
DEBUG: Cloud Function response body = {"result":200}
52+
tid=0x1fefccc00 +OnCallCompleted(...)
53+
tid=0x1fefccc00 -OnCallCompleted(...)
54+
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
55+
tid=0x1fefccc00 -Foo(...)
56+
tid=0x1fefccc00 Foo #1: value=200
57+
tid=0x1fefccc00 +Foo(...)
58+
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
59+
tid=0x1fefccc00 Call: `foo` +Call(...)
60+
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
61+
data: {"data":null}
62+
tid=0x1fefccc00 Call: `foo` -Call(...)
63+
tid=0x1fefccc00 ProcessEvents(100)
64+
DEBUG: Cloud Function response body = {"result":200}
65+
tid=0x1fefccc00 +OnCallCompleted(...)
66+
tid=0x1fefccc00 -OnCallCompleted(...)
67+
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
68+
tid=0x1fefccc00 -Foo(...)
69+
tid=0x1fefccc00 Foo #2: value=200
70+
tid=0x1fefccc00 +FooAsync(...)
71+
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
72+
tid=0x1fefccc00 Call: `foo` +Call(...)
73+
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
74+
data: {"data":null}
75+
tid=0x1fefccc00 Call: `foo` -Call(...)
76+
DEBUG: Cloud Function response body = {"result":200}
77+
tid=0x16b7fb000 +OnCallCompleted(...)
78+
tid=0x16b7fb000 FooAsync #1: value=200
79+
tid=0x16b7fb000 -OnCallCompleted(...)
80+
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
81+
tid=0x1fefccc00 -FooAsync(...)
82+
tid=0x1fefccc00 +FooAsync(...)
83+
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
84+
tid=0x1fefccc00 Call: `foo` +Call(...)
85+
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
86+
data: {"data":null}
87+
tid=0x1fefccc00 Call: `foo` -Call(...)
88+
DEBUG: Cloud Function response body = {"result":200}
89+
tid=0x16b7fb000 +OnCallCompleted(...)
90+
tid=0x16b7fb000 FooAsync #2: value=200
91+
tid=0x16b7fb000 -OnCallCompleted(...)
92+
tid=0x1fefccc00 -Cloud::Call(`foo`, ...)
93+
tid=0x1fefccc00 -FooAsync(...)
94+
tid=0x1fefccc00 +FooAsync(...)
95+
tid=0x1fefccc00 +Cloud::Call(`foo`, ...)
96+
tid=0x1fefccc00 Call: `foo` +Call(...)
97+
DEBUG: Calling Cloud Function with url: 127.0.0.1:5001/functions-futures-async-nested/us-central1/foo
98+
data: {"data":null}
99+
tid=0x1fefccc00 Call: `foo` -Call(...)
100+
DEBUG: Cloud Function response body = {"result":200}
101+
tid=0x16b7fb000 +OnCallCompleted(...)
102+
tid=0x16b7fb000 FooAsync #3: value=200
103+
tid=0x16b7fb000 +FooAsync(...)
104+
tid=0x16b7fb000 +Cloud::Call(`foo`, ...)
105+
^C
106+
```
107+
108+
Note at the end that the app hung indefinitely before the 6th `DEBUG: Calling Cloud Function` was logged and I had to Ctrl-Break.
109+
110+
I suspect this has something to do with the muxtex(s)/semaphore(s) used to track pending Futures and their OnCompletion.
111+
112+
One **admittedly not directly related** hint I got about this was from https://github.com/firebase/firebase-cpp-sdk/issues/51#issuecomment-654535769
113+
> 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.
114+
115+
It seems like the official code could stand for a bit more verbose logging around these areas... but what do I know?
116+
117+
I am not certain if these issues are [closely?] related:
118+
* https://github.com/firebase/firebase-cpp-sdk/issues/61
119+
* ...
120+
121+
## To test/run/use...
122+
123+
### firebase functions
124+
```bash
125+
cd firebase
126+
```
127+
128+
To run in emulator:
129+
```bash
130+
firebase emulators:start --only functions
131+
```
132+
133+
To deploy to firebase cloud:
134+
```bash
135+
firebase deploy --only functions
136+
```
137+
138+
### app
139+
140+
To build:
141+
```bash
142+
cd app
143+
144+
# once, or whenever firebase-cpp-sdk changes
145+
pushd libs
146+
./get-firebase-cpp-sdk.sh
147+
popd
148+
149+
mkdir -p build && cd $_
150+
151+
# Define an Android app in your firebase project and download its google-services.json
152+
cp /path/to/your/google-services.json .
153+
154+
# once, or whenever CMakeLists.txt changes
155+
cmake ..
156+
157+
# whenever code changes
158+
cmake --build .
159+
```
160+
161+
To run [and use firebase emulator by default]:
162+
```bash
163+
./FirebaseExample
164+
```
165+
166+
To run and not use emulator [use firebase cloud]:
167+
```bash
168+
./FirebaseExample --no-emulator
169+
```

app/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
google-services*.json

app/CMakeLists.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
# Set the project name and version
4+
project(FirebaseExample VERSION 1.0)
5+
6+
# Specify the C++ standard
7+
set(CMAKE_CXX_STANDARD 11)
8+
set(CMAKE_CXX_STANDARD_REQUIRED True)
9+
10+
# Add the executable
11+
add_executable(${PROJECT_NAME} main.cpp)
12+
13+
# "`firebase_app` is required and must always be listed last"
14+
set(firebase_libs firebase_app_check firebase_auth firebase_firestore firebase_functions firebase_app)
15+
if(APPLE)
16+
set(ADDITIONAL_LIBS
17+
gssapi_krb5
18+
pthread
19+
"-framework CoreFoundation"
20+
"-framework Foundation"
21+
"-framework GSS"
22+
"-framework Security"
23+
"-framework SystemConfiguration")
24+
elseif(MSVC)
25+
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 iphlpapi psapi userenv)
26+
else()
27+
set(ADDITIONAL_LIBS pthread)
28+
endif()
29+
30+
set(FIREBASE_SDK_DIR ${CMAKE_SOURCE_DIR}/libs/firebase_cpp_sdk)
31+
add_subdirectory(${FIREBASE_SDK_DIR} EXCLUDE_FROM_ALL)
32+
target_include_directories(${PROJECT_NAME} PRIVATE ${FIREBASE_SDK_DIR}/include)
33+
target_link_libraries(${PROJECT_NAME} PRIVATE ${firebase_libs} ${ADDITIONAL_LIBS})

app/libs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
firebase_cpp_sdk

app/libs/get-firebase-cpp-sdk.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
# https://api.github.com/repos/firebase/firebase-cpp-sdk/releases/latest
4+
# https://github.com/firebase/firebase-cpp-sdk/releases/latest
5+
6+
SDK_VERSION="12.1.0"
7+
ZIP_FILE="firebase_cpp_sdk_${SDK_VERSION}.zip"
8+
URL="https://dl.google.com/firebase/sdk/cpp/${ZIP_FILE}"
9+
FIREBASE_CPP_SDK_DIR="./firebase_cpp_sdk"
10+
11+
rm -rf ${FIREBASE_CPP_SDK_DIR}
12+
mkdir -p ${FIREBASE_CPP_SDK_DIR}
13+
14+
pushd ${FIREBASE_CPP_SDK_DIR} >/dev/null
15+
16+
echo "Downloading Firebase C++ SDK version ${SDK_VERSION}..."
17+
curl -L ${URL} -o ${ZIP_FILE}
18+
19+
echo "Extracting ${ZIP_FILE}..."
20+
extraction_inclusions=(
21+
"include/*"
22+
"libs/linux/x86_64/cxx11/*"
23+
"libs/darwin/universal/*"
24+
"libs/windows/*/MD/x64/*"
25+
"CMakeLists.txt"
26+
"NOTICES"
27+
"readme.md"
28+
)
29+
BASE_DIR_NAME=$(basename ${FIREBASE_CPP_SDK_DIR})
30+
prefixed_paths=()
31+
for path in "${extraction_inclusions[@]}"; do
32+
prefixed_paths+=("${BASE_DIR_NAME}/${path}")
33+
done
34+
unzip ${ZIP_FILE} "${prefixed_paths[@]}" -d ..
35+
36+
echo "Cleaning up..."
37+
rm ${ZIP_FILE}
38+
39+
popd >/dev/null
40+
41+
echo "Firebase C++ SDK setup complete."

0 commit comments

Comments
 (0)