firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.01k stars 929 forks source link

Admin SDK inconsistent transaction behavior in emulator #3928

Open johnnyoshika opened 2 years ago

johnnyoshika commented 2 years ago

[REQUIRED] Environment info

firebase-tools: 9.16.0

Platform: Windows

[REQUIRED] Test case

Example script to demonstrate the bug:

const admin = require('@firebase/testing');
const db = admin
  .initializeAdminApp({ projectId: 'project-id' })
  .firestore();

const sleep = milliseconds => {
  return new Promise(resolve => setTimeout(resolve, milliseconds));
};

const docPath = 'widgets/WaAYg8LtD5PTteX8tmOA';

(async function () {
  console.log('Set count to 0');
  await db.doc(docPath).set({
    count: 0,
  });

  try {
    await db.runTransaction(async transaction => {
      const document = await transaction.get(db.doc(docPath));
      console.log(
        'Fetched document with count',
        document.data().count,
      );

      console.log('Sleep start');
      await sleep(10000);
      console.log('Sleep end');

      console.log('Try to set count to ', document.data().count + 1);
      transaction.set(
        db.doc(docPath),
        { count: document.data().count + 1 },
        { merge: true },
      );
    });
  } catch (error) {
    console.log('error', error);
  }
})();

[REQUIRED] Steps to reproduce

Execute the script above and while the process is sleeping, change the document count field to 10 using the Firestore Emulator Console. Here's a diagram of the steps:

image

[REQUIRED] Expected behavior

The expected behavior is that it behaves exactly the same as Firestore in the cloud. In other words, the expected behavior is that the transaction holds a pessimistic lock on the document. When running the same script against Firestore in the cloud, if the document count field is set to 10 while the script is sleeping, it gets blocked and waits until the transaction completes. So in other words, the order of operation is:

count = 0
count = 10 (this gets blocked so doesn't get committed until the transaction ends)
count = 1
count = 10 (this now gets committed after the transaction ends)

The final result is count == 10.

[REQUIRED] Actual behavior

When using the emulator, the transaction does NOT hold a pessimistic lock on the document. Instead, it operates optimistically and reruns the transaction on concurrent edit. In other words, the order of operation is:

count = 0
count = 10 (this succeeds)
count = 1 (this fails and the transaction reruns)
count = 11 (this succeeds)

The final result is count == 11.

google-oss-bot commented 2 years ago

This issue does not have all the information required by the template. Looks like you forgot to fill out some sections. Please update the issue with more information.

johnnyoshika commented 2 years ago

Issue updated with all sections completed

abeisgoat commented 2 years ago

Thanks for the thorough report. We'll take a look.

weixifan commented 2 years ago

I filed an internal b/211321190 to track this.

johnnyoshika commented 2 years ago

@weixifan were you able to reproduce it?