Skip to content

randomly freezes for 45 seconds when getDocs after update transaction #8474

Closed
@mochiya98

Description

@mochiya98

Operating System

macOS Sonoma 14.4

Environment (if applicable)

Chrome 127.0.6533.100

Firebase SDK Version

10.13.1

Firebase SDK Product(s)

Firestore

Project Tooling

No-bundle (using cdn to reproduce)

Detailed Problem Description

  • Update data.
  • GetDocs before updated data is fully reflected onSnapshot.
  • Random freeze.
  • persistentLocalCache and security rules trigger this defect with high probability.

Steps and code to reproduce issue

  1. create new firebase project, and setup firestore.
  2. set security rules as below:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function getSheetRule(sheet_id) {
      return get(/databases/$(database)/documents/sheet_rules/$(sheet_id)).data;
    }
    match /sheets/{sheet_id}/{document=**} {
      allow read: if getSheetRule(sheet_id).read;
      allow create: if getSheetRule(sheet_id).create;
      allow update: if getSheetRule(sheet_id).update;
      allow delete: if getSheetRule(sheet_id).delete;
    }
    match /sheet_rules/{sheet_id} {
      allow read, write: if true;
    }
  }
}
  1. save the following as html, open it, and check the console in the developer Tools:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <script type="module">
      import { initializeApp } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-app.js";
      import {
        initializeFirestore,
        runTransaction,
        onSnapshot,
        setDoc,
        doc,
        collection,
        getDocs,
        where,
        deleteDoc,
        query,
        persistentLocalCache,
        persistentSingleTabManager,
      } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-firestore.js";

      // TODO: Replace the following with your app's [Firebase project configuration](https://firebase.google.com/docs/web/learn-more?hl=ja#config-object)
      const firebaseConfig = {
        // ..
      };
      const app = initializeApp(firebaseConfig);
      const firestore = initializeFirestore(app, {
        localCache: persistentLocalCache({
          tabManager: persistentSingleTabManager({}),
        }),
      });

      const nanoid = () => {
        const chars =
          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        let id = "";
        for (let i = 0; i < 21; i++) {
          id += chars[Math.floor(Math.random() * chars.length)];
        }
        return id;
      };
      let idA = "TjmWMWv7YsteLgkRMcRgA";
      let idB = "rmRSk7ya35bZHssCncdlg";
      let idC = "OEYRzjFB5NqhejLOXixna";
      let firstRecv = true;
      const sheetId = "hxP3Qe7UywllnEPMqmn9F";
      const docSheet = doc(firestore, "sheets", sheetId);
      const docItem = (id) => doc(firestore, "sheets", sheetId, "items", id);
      const colItems = collection(firestore, "sheets", sheetId, "items");
      const flowLog = (msg, ct = false) =>
        console.log(
          `%c${msg}`,
          `color:#000;background-color:${ct ? "#ff0" : "#6ff"};padding:0 5px`
        );
      const resetTestData = async () => {
        /* A -> B -> C -> null */
        await setDoc(docItem(idA), {
          id: idA,
          next_id: idB,
        });
        await setDoc(docItem(idB), {
          id: idB,
          next_id: idC,
        });
        await setDoc(docItem(idC), {
          id: idC,
          next_id: null,
        });
      };
      let tryCount = 0;
      const testRun = async () => {
        flowLog(`(${++tryCount}) trying...`, true);
        await runTransaction(firestore, async (t) => {
          /*
            A -> B -> C -> null
            to
            A -> null
          */
          await t.delete(docItem(idB));
          await t.delete(docItem(idC));
          await t.set(docItem(idA), {
            id: idA,
            next_id: null,
          });
          await t.set(docSheet, {
            last_run: nanoid(),
          });
        });
        flowLog("transaction completed");
        idB = nanoid();
        idC = nanoid();
        let getDocsStarted = Date.now();
        await getDocs(query(colItems, where("next_id", "==", null)));
        const duration = Date.now() - getDocsStarted;
        flowLog(`getDocs took ${duration}ms`);
        return duration;
      };
      const main = async () => {
        flowLog("------ START ------");
        while (true) {
          const duration = await testRun();
          if (duration > 1000 * 5) {
            console.error(`getDocs took too long (${duration}ms). exit.`);
            break;
          }
          const randomWait = new Promise((r) =>
            setTimeout(r, 500 + 100 * Math.floor(Math.random() * 10))
          );
          await randomWait;
          await resetTestData();
        }
      };
      setDoc(doc(firestore, "sheet_rules", sheetId), {
        create: true,
        delete: true,
        read: true,
        update: true,
      }).then(() => {
        onSnapshot(colItems, async (qs) => {
          const r = [];
          qs.forEach((doc) => {
            r.push({ id: doc.id, ...doc.data() });
          });
          console.group(
            `onSnapshot(/sheets/${sheetId}/items) size=${r.length}`
          );
          qs.docChanges().forEach((change) => {
            console.log(
              `%c${change.type}`,
              `color:${
                {
                  added: "#0f0",
                  modified: "#0ff",
                  removed: "#f60",
                }[change.type]
              };background-color:#000;`,
              change.doc.data()
            );
          });
          console.groupEnd();
          if (firstRecv) {
            firstRecv = false;
            for (const { id } of r) {
              await deleteDoc(docItem(id));
            }
            await resetTestData();
            await new Promise((r) => setTimeout(r, 2000));
            //console.clear();
            main();
          }
        });
      });
    </script>
  </body>
</html>
  1. you should be able to confirm that an error has occurred.

image

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions