aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.44k stars 2.13k forks source link

DataStore (sometimes) hangs on clear #12359

Closed alex-breen closed 11 months ago

alex-breen commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

Authentication, GraphQL API, DataStore, Storage

Amplify Categories

auth, storage, function, api, hosting

Environment information

``` # Put output below this line System: OS: Windows 10 10.0.22621 CPU: (6) x64 Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz Memory: 3.71 GB / 15.94 GB Binaries: Node: 18.18.0 - C:\Program Files\nodejs\node.EXE npm: 10.1.0 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 118.0.5993.71 Edge: Chromium (118.0.2088.46) Internet Explorer: 11.0.22621.1 npmPackages: @aws-amplify/ui-react: ^5.3.1 => 5.3.1 @aws-amplify/ui-react-internal: undefined () @aws-amplify/ui-react-storage: ^2.3.1 => 2.3.1 @babel/cli: ^7.21.0 => 7.22.15 @babel/core: ^7.21.4 => 7.22.15 @babel/plugin-proposal-private-property-in-object: ^7.21.11 => 7.21.11 (7.21.0-placeholder-for-preset-env.2) @babel/preset-env: ^7.21.4 => 7.22.15 @emotion/react: ^11.10.6 => 11.11.1 @emotion/styled: ^11.10.6 => 11.11.0 @mui/base: ^5.0.0-alpha.124 => 5.0.0-beta.14 @mui/icons-material: ^5.11.16 => 5.14.8 @mui/material: ^5.11.16 => 5.14.8 @prezly/slate-lists: ^0.97.0 => 0.97.0 @testing-library/jest-dom: ^5.16.5 => 5.17.0 @testing-library/react: ^13.4.0 => 13.4.0 @testing-library/user-event: ^13.5.0 => 13.5.0 aws-amplify: ^5.3.11 => 5.3.11 compromise: ^14.10.0 => 14.10.0 is-hotkey: ^0.2.0 => 0.2.0 (0.1.8) lodash: ^4.17.21 => 4.17.21 nlcst-to-string: ^3.1.1 => 3.1.1 react: ^18.2.0 => 18.2.0 react-avatar-editor: ^13.0.0 => 13.0.0 react-color: ^2.19.3 => 2.19.3 react-dom: ^18.2.0 => 18.2.0 react-dropzone: ^14.2.3 => 14.2.3 react-scripts: 5.0.1 => 5.0.1 retext: ^8.1.0 => 8.1.0 retext-keywords: ^7.2.0 => 7.2.0 retext-pos: ^4.1.0 => 4.1.0 slate-history: ^0.93.0 => 0.93.0 slate-hyperscript: ^0.77.0 => 0.77.0 slate-react: ^0.99.0 => 0.99.0 svg-gauge: ^1.0.7 => 1.0.7 uuid: ^9.0.0 => 9.0.0 (3.4.0, 8.3.2) web-vitals: ^2.1.4 => 2.1.4 npmGlobalPackages: @forge/cli: 4.1.1 atlas-connect: 0.8.1 firebase-tools: 10.7.0 mongodb-realm-cli: 2.4.2 npm-check-updates: 12.5.9 npm: 10.1.0 serve: 14.2.1 ```

Describe the bug

About 10% of the time DataStore hangs when executing DataStore.clear.

After a user logs in, I invoke DataStore.clear before DataStore.start, as recommended in the Amplify docs. Clear is invoked after the sign-in event is received.

Note, I have tried invoking "DataStore.start" before clear, but that usually triggers an ugly unhandled promise error on login.

Expected behavior

DataStore quickly clears.

Reproduction steps

  1. SIgn-in

It's possible that this typically occurs after the user has been signed out automatically due to inactivity. E.g., waking up the next day.

Code Snippet

// Put your code below this line.

    const [dataStoreRefreshed, setDataStoreRefreshed] = useState(true)

    const refreshDataStore = async () => {
        console.log('Clearing DataStore')
        await DataStore.clear()
        console.log('Starting DataStore')
        await DataStore.start()
        console.log('DataStore started')
        setDataStoreRefreshed(true)
    }

 useEffect(() => {
        // Create Hub listener to track auth signin and signout
        const listener = Hub.listen('auth', hubData => {
            const { event, data } = hubData.payload;
            console.log('Hub listener Auth event: ' + event)
            if ((event === 'signIn') || (event === 'autoSignIn')) {
                setDataStoreRefreshed(false)
                refreshDataStore()
            }
            if (event === 'signOut') {
                setDataStoreRefreshed(false)
            }
        })
        // Remove listener
        return () => { listener() }
    }, [])

    useEffect(() => {
        // Create listener for datastore sync events
        const listener = Hub.listen('datastore', async hubData => {
            const { event, data } = hubData.payload;
            console.log('Hub listener Datastore event: ' + event)
        })

        // Remove listener
        return () => { listener() }
    }, [])

// 
return (
<div>
  <Authenticator components={components}
      loginMechanisms={['email']}
      signUpAttributes={['email', 'name']}
      socialProviders={['google']}
      variation="modal"
      initialState={tab}
      formFields={formFields}
  >
      {({ signOut, user }) => (
          dataStoreRefreshed ? <App user={user} signOut={signOut} /> : <Fragment>Loading...</Fragment>
      )}
  </Authenticator>
</div>
)

Log output

image

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

chrisbonifacio commented 1 year ago

Hi @alex-breen 👋 thanks for opening this issue. Sounds like this issue might be difficult to find the right conditions to reproduce. Is there a particular reason why you clear data on signin rather than signout?

Also, when DataStore hangs on clearing, are you able to capture any logs? That might give some insight into why it's hanging.

alex-breen commented 1 year ago

Hi @chrisbonifacio. I clear on both sign-in and sign-out. I do that in part because I interpreted the docs as saying that was recommended. Also, I assumed that if Amplify's Authentication component forces a sign-out due to tokenRefresh_failure, my code might not be able to reliably catch the event and invoke DataStore.clear(). Or other cases that I can't think of that might result in a signed-out state but with data still present in the indexedDb.

BTW, I've also run into problems invoking DataStore.clear() while in a signed-in state where it sometimes throws the error: DataStore State Error: Tried to executeDataStore.observe()while DataStore was "Clearing". I'm pretty sure this was triggered when DataStore had a sync in progress while 'clear' was invoked. My solution is to a) check if Hub's sync status is 'ready' b) if not ready then I retry every 500ms. I'm not proud of that solution, but I couldn't think of anything better - if you can suggest a better solution, I'm all ears.

Regarding more logs, what should I do to pull or generate more verbose logs? I have the events from the Hub listener for Auth and DataStore (which I attached). My hunch is this is typically triggered when there is a tokenRefresh_failure.

alex-breen commented 1 year ago

This is related, but might be worth a separate issue.

Condition:

Error is thrown: datastore.ts:1484 Uncaught Error: DataStoreStateError: Tried to executeDataStore.observeQuery()while DataStore was "Clearing". This can only be done while DataStore is "Started" or "Stopped". To remedy: Ensure all calls tostop()andclear()have completed first. If this is not possible, retry the operation until it succeeds. at handler (datastore.ts:1484:1)

If I add a 1000ms pause between the "sign-in" event and invoking clear, everything works fine. Note, Hub listener does not report any DataStore events during this phase.

chrisbonifacio commented 1 year ago

Thank you for the reproduction steps, @alex-breen! We will look into this. In the meantime, I think performing a retry and/or delay is fine until we implement a permanent solution.

alex-breen commented 1 year ago

Thanks @chrisbonifacio. Based on a few tests, if I remove the Hub listener for DataStore events, the error I was seeing after entering the sign-in confirmation code no longer appears.

Error: datastore.ts:1484 Uncaught Error: DataStoreStateError: Tried to execute DataStore.observeQuery()while DataStore was "Clearing". This can only be done while DataStore is "Started" or "Stopped". To remedy: Ensure all calls tostop()andclear() have completed first. If this is not possible, retry the operation until it succeeds. at handler (datastore.ts:1484:1)

Listener code (which I've now removed):

useEffect(() => {
        // Create listener for datastore sync events
        const listener = Hub.listen('datastore', async hubData => {
            const { event, data } = hubData.payload;
            console.log('Hub listener Datastore event: ' + event)
        })

        // Remove listener
        return () => { listener() }
    }, [])
crpozo commented 1 year ago

I have noticed that if you have multiple tabs open, this causes DataStore.clear() to fail to terminate its processes because the other tab is where DataStore is started or working with the data. I close others tabs and works pretty well, just one tab opened. Working with DataStore is clearly a pain in the head.

I follow this post as well for clearing properly via promise https://stackoverflow.com/questions/74314691/aws-amplify-datastorestateerror-tried-to-execute-datastore-query-while-datast

chrisbonifacio commented 11 months ago

If anyone is experiencing similar issues with DataStore.clear please refer to @crpozo's comment regarding multiple tab behaviors as well as this comment describing how to ensure that clear has finished resolving before querying.