In my 2nd generation cloud function i have the following code:
export const roomEnter = functions.https.onCall(async (request: any) => {
// initial validation to verify user authentication and valid request data passes first
...
const roomRef = admin.database().ref(`rooms/${request.data.roomId}`);
const roomSnapshot = await roomRef.once("value");
const testDataExists = {
exists: roomSnapshot.exists(),
data: roomSnapshot.val()
};
functions.logger.log("[room enter] CHECK ROOM EXISTS:", JSON.stringify(testDataExists));
// In the logs, the data that exists DOES print to the console successfully
const transactionRef = admin.database().ref(`rooms/${request.data.roomId}`);
const transactionResult = await transactionRef.transaction((currentData) => {
functions.logger.log("[room enter] Transaction current data:", JSON.stringify(currentData));
// In the logs, `currentData` prints as `null`, why?
// As a result, the transaction never gets beyond this condition and fails i assume after it has exhausted all internal retries
if (currentData !== null) {
if (currentData.memberTotal) {
currentData.memberTotal += 1;
} else {
currentData.memberTotal = 1;
}
return currentData;
} else {
functions.logger.log("[room enter] Transaction needs to retry, room current data is null", {
roomId: request.data.roomId
});
return; // Returning undefined should cause Firebase to retry the transaction
}
});
if (transactionResult.committed) {
// never able to reach this point
} else {
functions.logger.log("[room enter] transaction to increment member total failed");
throw new functions.https.HttpsError("internal", "transaction to increment member total failed", {
customData: {
roomId: request.data.roomId
}
});
}
...
});
The test query i do immediately prior to the transaction is only there as a test to confirm the data exists.
And as noted in the code comments above, the transaction fails despite the reference path to existing data having been confirmed to exist.
Other than attempting to log errors everywhere possible, i am not sure how to troubleshoot this issue.
Other cloud functions i have which do not use transactions are able to read and write to the database without issue.
The only additional info i have aside from the logs, is that client side the error is reported to the client as a 500 internal server error along with the response which does include the error response containing the message transaction to increment member total failed.
With respect to my database security rules, as a test, in-order to rule this out as the cause i have set ".write": true for rooms and rooms/$roomId
So, why is currentData null inside the transaction?
What else can i do to troubleshoot?
>Solution :
Firebase Realtime Database transaction handlers will almost always be called with a null value in the mutable data initially, no matter what the actual value in the database is.
This is because the SDK immediately calls your handler with its best guess to the current value, and that’ll usually be null. Your code will need to handler this situation, so that the SDK can then send the null-and-new-value to the server, realize that its guess was wrong, and try again.
This has been covered quite a few times over the years, and probably explained better in some of those cases than I did here, so I recommend checking out:
- Firebase realtime database transaction handler gets called twice most of the time (including an ASCII art diagram of the flow)
- Firebase runTransaction not working – MutableData is null
- how to handle null values on firebase transaction api
- Firebase transaction reads null at path even when there is data at that path
- Avoid obtaining null currentData at first reading in a Firebase transaction