firebase / firebase-admin-node

Firebase Admin Node.js SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
1.59k stars 358 forks source link

`DocumentReference.create()` keeps trying to create an already existing document, leading to a hang in functions #2587

Open jellynoone opened 1 month ago

jellynoone commented 1 month ago

[READ] Step 1: Are you in the right place? ✅

[REQUIRED] Step 2: Describe your environment

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

  1. Create a new demo project via firebase init
  2. Select functions and firestore
  3. Run emulators through: firebase emulators:start --inspect-functions --project demo-me
  4. Visit http://127.0.0.1:5001/demo-me/us-central1/runMeTwice in browser
  5. Refresh

Relevant Code:

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "engines": {
    "node": "18"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "^12.1.0",
    "firebase-functions": "^5.0.0"
  },
  "private": true
}
//@ts-check
const { onRequest } = require("firebase-functions/v2/https");
const logger = require("firebase-functions/logger");
const { firestore, initializeApp } = require("firebase-admin");
const { initializeFirestore } = require("firebase-admin/firestore");

/** @type {firestore.Firestore} */
let _defaultFirestore;
function defaultFirestore() {
    return _defaultFirestore ??= initializeFirestore(initializeApp(), {
        preferRest: true,
    })
}

exports.runMeTwice = onRequest(async (_, res) => {
    try {
        const doc = defaultFirestore().collection("test").doc("smt");
        await doc.create({});
        logger.log("Created");
    } catch (error) {
        logger.log(error);
    } finally {
        res.end();
    }
});

Expected behavior

The second time the function is ran, it should fail.

Actual behavior

The first time the function is run the document is created. The second time, the function never completes.

Additionally, attaching to the node process, it seems the creation process keeps getting retried even though it should fail.

google-oss-bot commented 1 month ago

I found a few problems with this issue:

jellynoone commented 1 month ago

In addition, performing this in a transaction will eventually fail and terminate the function. However, attaching the debugger it shows, the transaction is still attempted multiple times. This seems incorrect.

exports.runMeTwice = onRequest(async (_, res) => {
    try {
        const firestore = defaultFirestore();
        await firestore.runTransaction(async (txn) => {
            txn.create(firestore.collection("test").doc("smt"), {});
        })
        logger.log("Created");
    } catch (error) {
        logger.log(error);
    } finally {
        res.end();
    }
});
cherylEnkidu commented 1 month ago

Hi @jellynoone ,

I will take a look at this and get back to you.

cherylEnkidu commented 1 month ago

@jellynoone

While investigating, there are some things can be tried as a workaround:

cherylEnkidu commented 1 month ago

This is the code I used to reproduce the issue against production, it throws Document already exists successfully.

const ref = randomCol.doc();
await ref.create({});
const snap1 = await ref.get();

await ref.create({});

Are you testing against production or emulator?

jellynoone commented 1 month ago

I'm testing this against the emulator. Did you try with the provided example? I think the preferRest: true, configuration option might be significant.

Tested this again against the emulator, without preferRest the function completes with an error. With preferRest the function hangs.

jellynoone commented 1 month ago

@cherylEnkidu Did you perhaps have a chance to reevaluate based on the additional information provided?

cherylEnkidu commented 3 weeks ago

Hi @jellynoone ,

I will continue the investigation this week, and I will reply the ticket as soon as I get any updates :)

jellynoone commented 1 week ago

Hi @cherylEnkidu were you able to take a look?

cherylEnkidu commented 1 week ago

Hi @jellynoone ,

Thanks for the waiting. I tried the same code you provided, but the process never hangs. Could you please provided a small repo app?

jellynoone commented 1 week ago

Attaching a small repro app.

export.zip

Additional notes:

jellynoone commented 1 week ago

@cherylEnkidu Attaching a video of the bug being reproduced.

https://github.com/firebase/firebase-admin-node/assets/77585130/d4bba6c7-4385-4165-bf79-3c677dce04ed

In the video I'm using firebase emulators:start --project demo-me to start the emulators without needing a real project. However, I tested this with a live Firestore instance and emulated functions and observed the same bug.

In addition, in the video I'm calling curl instead of the browser to make the process more streamlined.

jellynoone commented 4 days ago

@cherylEnkidu Do you need more information or anything else? Were you able to reproduce the issue?

cherylEnkidu commented 4 days ago

Hi @jellynoone ,

Thank you for your inputs! I will take a look at the code provided and get back to you.