question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Functions emulator can enter an infinite loop after a Firestore trigger

See original GitHub issue

[REQUIRED] Environment info

firebase-tools: 9.1.2

Platform: macOS 11.1 x86

[REQUIRED] Test case

This is a rather unusual bug that appears to be the result of some kind of internal race condition in the functions emulator when it comes to deleting documents. This is triggered from using an admin client of the @firebase/rules-unit-testing library triggering an emulated function from firebase-functions, but I believe the issue resides in the emulator itself, hence this ticket. I have been able to reproduce this issue on Node 12 and Node 14.

Firstly, a Firestore function trigger is needed. This is what will enter the infinite loop. The trigger itself is an onCreate trigger that should only be called when a document is initially created. It will immediately delete anything written to some collection in the database. I have been able to reproduce the issue for both root collections and sub-collections. We will use a root collection for simplicity.

// Firebase functions project
// index.js
const functions = require("firebase-functions");

exports.bugTestFunction = functions.firestore
    .document("testing-bug/{mydoc}")
    .onCreate(async (snapshot, context) => {
        console.log("data", JSON.stringify(snapshot.data()));
        
        await snapshot.ref.delete();
    });

Now we set some data and delete it ourselves. I have created a script that appears to trigger the issue every time, using the @firebase/rules-unit-testing library. Ensure that the project ID matches the project ID of the firebase functions project. Also note that we need to call the same code twice to trigger 2 invocations of the target function. The bug does not occur with just 1 call.

// Separate script for triggering the bug
// bug-script.js
const firebase = require("@firebase/rules-unit-testing");

const admin = firebase.initializeAdminApp({
    // project ID MUST match that of your firebase functions project
    projectId: "my-project",
});

async function run() {
    const ref = admin.firestore().collection("testing-bug");
    console.log("setting value...");
    const newRef = await ref.add({ test: 123 });
    await new Promise((r) => setTimeout(r, 3000));
    console.log("deleting value...");
    await newRef.delete();
}

// run twice for the bug
run().then(() => run());

[REQUIRED] Steps to reproduce

After using the steps described above to setup the test environment, we can reproduce the bug.

  1. Open a terminal and start the Firebase Emulator Suite. Functions and Firestore should be enabled.
  2. Leaving the emulator running, open another terminal and run the test code above using node bug-script.js.
  3. Look back at the emulator. We see it has entered an infinite loop with a repeating log as shown below. You can kill the emulator by sending Ctrl + C.
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s
i  functions: Beginning execution of "bugTestFunction"
i  functions: Finished "bugTestFunction" in ~1s

[REQUIRED] Expected behavior

The emulator should not enter an infinite loop. The document at the reference should be deleted after 1 function invocation. Even if the local client ‘beats’ the function trigger to deleting the document, the trigger should still only be called once, it will now just point to a deleted document.

[REQUIRED] Actual behavior

The document is deleted, but the emulator appears to call the trigger function in a loop. The snapshot.data() will contain undefined for each of the additional triggers, implying that the trigger is repeatedly called on the already deleted document.

There is a possibility that this issue also occurs in the live Functions and Firestore environment, but I have not attempted this for fear of exhausting my billing budget! If anyone can also confirm that this only occurs in the emulator that would help to put me at ease!

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
samtsterncommented, Jan 12, 2021

Asigning to @rosalyntan who is on call for issue this week.

1reaction
samtsterncommented, Jan 12, 2021

@bradleymackey thank you for the details! I was able to reproduce this, it does seem like a bug to me. Let me investigate some more.

Read more comments on GitHub >

github_iconTop Results From Across the Web

firebase - will this google cloud function cause an infinite loop ...
I think this will cause an infinite loop because, this code await change.after.ref.update({somedata:'data'}); should trigger the function again, ...
Read more >
Cloud Firestore triggers | Cloud Functions for Firebase
With Cloud Functions, you can handle events in Cloud Firestore with no need to update client code. You can make Cloud Firestore changes...
Read more >
Google Cloud Firestore Triggers | Cloud Functions ...
Cloud Functions can handle events in Cloud Firestore in the same Cloud project as the function. ... Don't perform a(nother) write to avoid...
Read more >
What if I accidentally run an infinite loop that reads data from ...
Or... let's say my code was fine and had no infinite loops. Someone can just open up dev tools on my website and...
Read more >
Firebase: Cloud Functions Cheatsheet - Viblo
Cloud Functions support the following triggers for Realtime database ... where a function triggers itself, must be avoided (to avoid infinite loop) ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found