Skip to content

Defer check whether auth is available #4845

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

Merged
merged 11 commits into from
May 5, 2021
Merged

Conversation

schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Apr 28, 2021

Adds support for calling getFirestore before calling getAuth.

Tested manually (our integration tests do not use auth).

@changeset-bot
Copy link

changeset-bot bot commented Apr 28, 2021

⚠️ No Changeset found

Latest commit: be1fa3c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@google-oss-bot
Copy link
Contributor

google-oss-bot commented Apr 28, 2021

Binary Size Report

Affected SDKs

  • @firebase/firestore

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 283 kB 284 kB +604 B (+0.2%)
    esm2017 226 kB 226 kB +209 B (+0.1%)
    main 530 kB 531 kB +955 B (+0.2%)
    module 283 kB 284 kB +604 B (+0.2%)
    react-native 226 kB 226 kB +209 B (+0.1%)
  • @firebase/firestore-exp

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 223 kB 224 kB +209 B (+0.1%)
    main 506 kB 507 kB +955 B (+0.2%)
    module 223 kB 224 kB +209 B (+0.1%)
    react-native 224 kB 224 kB +209 B (+0.1%)
  • @firebase/firestore-lite

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 72.0 kB 72.4 kB +444 B (+0.6%)
    main 146 kB 147 kB +1.10 kB (+0.8%)
    module 72.0 kB 72.4 kB +444 B (+0.6%)
    react-native 72.2 kB 72.6 kB +459 B (+0.6%)
  • @firebase/firestore/bundle

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 289 kB 290 kB +604 B (+0.2%)
    esm2017 176 kB 176 kB +209 B (+0.1%)
    main 526 kB 527 kB +955 B (+0.2%)
    module 289 kB 290 kB +604 B (+0.2%)
    react-native 176 kB 176 kB +209 B (+0.1%)
  • @firebase/firestore/memory

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 215 kB 216 kB +604 B (+0.3%)
    esm2017 172 kB 172 kB +209 B (+0.1%)
    main 324 kB 325 kB +479 B (+0.1%)
    module 215 kB 216 kB +604 B (+0.3%)
    react-native 172 kB 172 kB +209 B (+0.1%)
  • @firebase/firestore/memory-bundle

    Type Base (c34ac7a) Head (eee135e) Diff
    browser 224 kB 224 kB +604 B (+0.3%)
    esm2017 176 kB 176 kB +209 B (+0.1%)
    main 321 kB 322 kB +479 B (+0.1%)
    module 224 kB 224 kB +604 B (+0.3%)
    react-native 176 kB 176 kB +209 B (+0.1%)
  • firebase

    Type Base (c34ac7a) Head (eee135e) Diff
    firebase-firestore.js 332 kB 332 kB +528 B (+0.2%)
    firebase-firestore.memory.js 266 kB 267 kB +528 B (+0.2%)
    firebase.js 875 kB 875 kB +532 B (+0.1%)

Test Logs

@google-oss-bot
Copy link
Contributor

google-oss-bot commented Apr 28, 2021

Size Analysis Report

Affected Products

No changes between base commit (c34ac7a) and head commit (f1d081d).

@schmidt-sebastian schmidt-sebastian changed the title Defer check whether auth is avaiable Defer check whether auth is available Apr 29, 2021
Copy link
Member

@Feiyang1 Feiyang1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got an internal assertion failure unexpected state if auth is never initialized, or initialized too late. And requests seem to be queued indefinitely until Auth is initialized.

@schmidt-sebastian
Copy link
Contributor Author

The assertion should be gone as of 1e75d78

@Feiyang1
Copy link
Member

Thanks. The error went away. Now I found a use case where Firestore still doesn't wait for auth initialization:

const firestore = getFirestore(firebaseApp);
setLogLevel("debug");

// this getDoc() is important for repro. If we don't make a getDoc here, the getDoc in setTimeout will succeed.
getDoc(doc(firestore, 'coll/New York')).then(v => {
    console.log('sync firestore get', v.data());
});


setTimeout(() => {
    // assume there is a valid user
    getAuth(); 
    
    // getDoc fails with permission denied, but it should succeed since it's called after getAuth().
    getDoc(doc(firestore, 'coll/New York')).then(v => {
        console.log('Async firestore get', v.data());
    });
}, 500);

For repro, you can clone https://github.com/Feiyang1/playground/tree/fs-auth-init and use the project in the rollup directory.

You mentioned we may need to queue up work that uses the user, or even the auth, is that what we need to do to make it work?

@schmidt-sebastian
Copy link
Contributor Author

I think this will be solved by your "callback" PR that replaces the Promise in the initialization. Do you still have a link to that? I can apply this here or we can merge that PR before we test here further.

@Feiyang1
Copy link
Member

Yes! https://github.com/firebase/firebase-js-sdk/commits/fei-firestore-wait

You may need to resolve some merge conflicts. Thanks!

@Feiyang1
Copy link
Member

I took the liberty to merge the component change into your PR, but It still doesn't work.

@schmidt-sebastian
Copy link
Contributor Author

I took the liberty to merge the component change into your PR, but It still doesn't work.

I will take over from here :)

@schmidt-sebastian
Copy link
Contributor Author

Updated. Tested with no getDoc initially, with getDoc initially and with no getAuth.

@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/optionalauth branch from 44f5c2f to 6e9133e Compare May 4, 2021 16:19
@schmidt-sebastian schmidt-sebastian requested a review from jsdt as a code owner May 4, 2021 16:19
@schmidt-sebastian
Copy link
Contributor Author

This is now completely rewritten. The SDK now blocks on the availability of the use in the Credential Provider itself, which allows the SDK to block twice: Once when we check whether Auth is available at startup time and then once again if Auth is loaded later. We also synchronously invoke the user change handler (right as we release the block), which ensures that the user change gets executed before everything else.

@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/optionalauth branch 3 times, most recently from 3e3015e to f0355ed Compare May 4, 2021 16:37
@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/optionalauth branch from f0355ed to 99b79f6 Compare May 4, 2021 16:37
Copy link
Member

@Feiyang1 Feiyang1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand most parts, but left a couple of questions.

I think we also need to do something similar in Database.

// raised. The initial event itself is invoked synchronously in
// `awaitTokenAndRaiseInitialEvent()`.
this.asyncQueue.enqueueRetryable(() => {
this.changeListener(this.currentUser);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the registerAuth(), we do

        this.awaitTokenAndRaiseInitialEvent();
        this.auth.addAuthTokenListener(this.tokenListener);

We already invoke this.changeListener in the asyncQueue in awaitTokenAndRaiseInitialEvent, should we not do it again in this.tokenListener?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I addressed this. I am not able to verify this right now, but will do a test run later today. BTW, this was meant to be addressed by the buggy code around initialEventRaised

* Whether the initial token event has been raised. This can go back to
* `false` if Firestore first starts without Auth and Auth is loaded later.
*/
private initialEventRaised: boolean = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable doesn't seem to do anything, because it's only set in awaitTokenAndRaiseInitialEvent which is synchronous. It's not used in awaitTokenAndRaiseInitialEvent() itself and is always set to true at the end of the function, so it seems the outside world will always see this variable as true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this bothers me. It is meant to prevent double-firing of the change listener. I tested that it doesn't fire twice manually, but what you say is also true - this variable and its check do not do anything. I have fixed the variable and am now resetting it on the queue rather than synchronously.

// Create a new deferred Promise that gets resolved when we receive the
// next token. Ensure that all previous Promises also get resolved.
const awaitToken = new Deferred<void>();
void awaitToken.promise.then(() => awaitToken.resolve());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then only fires if awaitToken resolves, why do we resolve it again in then? The comment suggests it ensures all previous Promises also get resolved? But I don't get how that works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was meant to resolve the existing this.receivedUser Promise if another one was created later on. I changed how the listener gets called if auth is not used, and am no longer using the Promise twice. This allowed me to remove this logic.

* `awaitTokenAndRaiseInitialEvent()` is also used to block Firestore until
* Auth is fully initialized.
*
* This function also invokes the change listener synchronously once a token
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean asynchronously, since it happens in the asyncQueue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant "synchronously with the code that resolves the token". I have reworded it.

}
}, 0);

this.awaitTokenAndRaiseInitialEvent();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this.asyncQueue set at this point?

My understanding is it is not, so awaitTokenAndRaiseInitialEvent doesn't do anything except for setting this.invokeChangeListener = false; which will lead to wrong behavior if auth is not available - the following code in this.tokenListener` will not execute:

      if (this.invokeChangeListener) {
        this.asyncQueue!.enqueueRetryable(() =>
          this.changeListener(this.currentUser)
        );
      }

Copy link
Member

@Feiyang1 Feiyang1 May 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work in code though. Firestore will not receive a null user when auth is not initialized, but nothing seems to break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the logic for invokeChangeListener to only set it to true if the AsyncQueue is available. This should address this concern.

@schmidt-sebastian
Copy link
Contributor Author

This should be ready for review again.

if (auth) {
registerAuth(auth);
} else if (this.invokeChangeListener) {
// If auth is still not available, invoke tokenListener once with null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment needs update.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Feiyang1
Copy link
Member

Feiyang1 commented May 5, 2021

Tested the following manually, and they all worked. Yay!

  • initializeAuth before getFirestore
  • initializeAuth after getFirestore synchronously
  • initializeAuth after getFirestore asynchronously
  • initializeAuth never called

@schmidt-sebastian schmidt-sebastian merged commit a13f5a0 into master May 5, 2021
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/optionalauth branch May 5, 2021 17:05
@firebase firebase locked and limited conversation to collaborators Jun 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants