Skip to content

runTransaction isn't awaited #6915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Lyokone opened this issue Dec 29, 2022 · 16 comments
Closed

runTransaction isn't awaited #6915

Lyokone opened this issue Dec 29, 2022 · 16 comments

Comments

@Lyokone
Copy link

Lyokone commented Dec 29, 2022

First reported here: firebase/flutterfire#10153

[REQUIRED] Describe your environment

  • Operating System version: macOS Latest
  • Browser version: Chrome latest
  • Firebase SDK version: 9.15
  • Firebase Product: firestore

[REQUIRED] Describe the problem

Firestore doesn't retrieve the latest data when using 'getDoc' on a document just after this document was modified through a transaction and a stream to listen on the same document is active.

The content of the retrieved document in the 'getDoc' call should be updated.
This issue is not happening if we are not listening to the document OR if the document is not updated through a transaction.

Steps to reproduce:

It happens on true Firebase instances but is harder to reproduce locally on an emulator.

Relevant Code:

import { initializeApp } from "firebase/app";
import {
  doc,
  getDoc,
  getFirestore,
  onSnapshot,
  runTransaction,
} from "firebase/firestore";

var config = {
  ...
};

// Initialize Firebase
var app = initializeApp(config);
const db = getFirestore(app);

const test_doc = doc(db, "test_10153", "test");

const unsub = onSnapshot(test_doc, (doc) => {
  console.log("Current data: ", doc.data());
});

for (let i = 0; i < 10; i++) {
  var newPopulation;

  await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(test_doc);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }

    newPopulation = sfDoc.data().population + 1;
    console.log("Updating to:", newPopulation);
    transaction.update(test_doc, { population: newPopulation });
  });

  const data = await getDoc(test_doc);

  console.log("Expected: ", newPopulation);
  console.log("Actual: ", data.data().population);

  console.log("---------");
}

unsub();
@cherylEnkidu
Copy link
Contributor

Hi,

Thank you for filling up the ticket! The team is not available for support during the holiday break and we will get back to you in the new year. Happy holiday!

@ishowta
Copy link

ishowta commented Jan 4, 2023

It may be the same similar problem as #5895

@pldelattre
Copy link

Hi @cherylEnkidu , were you able to investigate this issue ?

@cherylEnkidu
Copy link
Contributor

Hi @pldelattre ,

I tested the code sample you provide on the production environment but the output is same between Expected and Actual. Can you provide a minimum reproduce app?

@pldelattre
Copy link

Interesting.
I'm not the one who wrote the JS repro code (@Lyokone did - I provided a repro code for flutter here : firebase/flutterfire#10153).

@cherylEnkidu Can you confirm the expected behavior for an awaited transaction is to be awaited until it is commited to the DB ? And that any get call after the awaited transaction should retrieve the updated data and never the data from before the transaction ?

@cherylEnkidu
Copy link
Contributor

Hi @Lyokone @pldelattre ,

In this block of code, I can confirm what you said is expected behaviour.

@pldelattre
Copy link

Thank you cherylEnkidu.
That confirms a bug somewhere because @Lyokone was able to get different values for Expected and Actual running the js code above. And I was able to get different values for Expected and Actual with equivalent Flutter code on both Flutter Web and Flutter Android (firebase/flutterfire#10153 (comment)).

@ishowta
Copy link

ishowta commented Jan 12, 2023

I was able to reproduce the similar problem by calling updateDoc in another app instead of using runTransaction.

export const app = initializeApp(firebaseConfig, 'app')
export const firestore = initializeFirestore(app, {})

export const app_sub = initializeApp(firebaseConfig, 'app_sub')
export const firestore_sub = initializeFirestore(app_sub, {})

const run = async () => {
  const test_doc = doc(firestore, 'tests', 'test')
  const test_doc_sub = doc(firestore_sub, 'tests', 'test')
  await setDoc(test_doc, { population: 0 })

  const unsub = onSnapshot(test_doc, (doc) => {
    console.log('Current data: ', doc.data())
  })

  let newPopulation = 0
  for (let i = 0; i < 10; i++) {
    newPopulation = newPopulation + 1
    console.log('Updating to:', newPopulation)
    await updateDoc(test_doc_sub, { population: newPopulation })

    const data = await getDoc(test_doc)

    console.log('Expected: ', newPopulation)
    console.log('Actual: ', data.data()?.population)

    console.log('---------')
  }

  unsub()
}

run()

This problem is probably caused by firebase-js-sdk caching the retrieved document. Under normal situation, calling updateDoc or similar will immediately update the local document via latency compensation (https://firebase.google.com/docs/firestore/query-data/listen#events-local-changes), but if runTransaction is used or the update is performed on another instance or server, maybe, latency compensation is not performed, and the cache is retrieved depending on the timing..

If the above reason is correct, the problem is similar to this one (#5895). In the case of this Issue, it is confirmed that depending on the timing, the problem may occur even if there is no transaction and in the same application.

Translated with www.DeepL.com/Translator (free version)

@google-oss-bot
Copy link
Contributor

Hey @Lyokone. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@pldelattre
Copy link

Hi @Lyokone , have you been able to reproduce issue with @ishowta code above ?

@Lyokone
Copy link
Author

Lyokone commented Jan 20, 2023

@pldelattre Did not try @ishowta code, but the result seem to point to an issue in the JS SDK

@cherylEnkidu
Copy link
Contributor

I tried with the code @ishowta , unfortunately I still cannot reproduce on my end.

@ishowta
Copy link

ishowta commented Feb 13, 2023

@cherylEnkidu Here is the code to reproduce in codesandbox https://codesandbox.io/p/sandbox/https-g.yxqyang.asia-firebase-firebase-js-sdk-issues-6915-l2zf6m

@cherylEnkidu
Copy link
Contributor

Hi @ishowta ,

I successfully reproduced the problem use the code you provided. Thank you for spending time! I will take a close look to find out what's going on.

@google-oss-bot
Copy link
Contributor

Hey @Lyokone. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@cherylEnkidu
Copy link
Contributor

Hi,

Thanks for the all the effort helping us diagnose the issue.

The reason why users get mismatch number is because users have onSnapShot register.

When onSnapShot present, when you call getDoc (or even getDocFromServer in this case) the SDK would get the number from cache because it believes the number inside the cache is up to date.

Unfortunately, firestore do not guarantee read-your-own-writes between transaction and non-transaction operations. We do guarantee that within each of those.

So if in your case the mismatch is unacceptable, you might need to consider removing onSnapShot.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants