Open mks11 opened 9 months ago
Hello, @mks11 👋. Sorry to hear you're experiencing this blocker. We're looking into this right now, but this could possibly be related to #12359 as well. That issue was experiencing similar problems when attempting to clear while an Auth event is happening.
Can you see if this comment from that issue (which also references 2 comments) helps at all? It details some steps to ensure that the Datastore.clear()
has finished resolving before querying.
@mks11, can you also help clarify where the Datastore.query()
call is being made that is potentially throwing this error? I don't see it in the code snippets provided, but that may give us some insight as to why this is happening.
Hi @cwomack, 👋 thank you so much for looking into it, this is the root layout for our NextJS application. removed some of the code.
If you check the bottom of the file, there is the observeQuery
snippet. This is the subscriber that is inside a file following the convention of App Router from NextJS 13, like so src
> app
> (reports)
>page.tsx
, whereas the above part of the code (also commented) is at src
> app
> layout.tsx
.
function Page() {
const orgID = getOrgId(useUser()); // added snippet below
const [reports, setReports] = useState<Report[]>([]);
const router = useRouter();
useEffect(() => {
const sub = DataStore.observeQuery(
Report,
(r) => r.organizationID.eq(orgID),
{
sort: (s) => s.createdAt(SortDirection.DESCENDING),
}
).subscribe(({ items }) => {
setReports(items);
});
return () => sub.unsubscribe();
}, [orgID]);
return <div> (code removed) </div>
}
Amplify.configure(amplifyconfig, {
ssr: true,
});
DataStore.configure({
errorHandler: (err) => {
console.warn("Datastore err", err);
},
authModeStrategyType: AuthModeStrategyType.DEFAULT,
conflictHandler: async (data: SyncConflict) => {
console.log("@@@ conflict @@@ data", data);
return DISCARD;
},
maxRecordsToSync: 50000,
});
function RootLayout({ children }: { children: React.ReactNode }) {
const [isDSReady, setDSReady] = useState(false);
Hub.listen("auth", async (data) => {
switch (data.payload.event) {
case "signedIn":
console.log("user signed in");
break;
case "signedOut":
setDSReady(false);
await DataStore.stop();
await DataStore.clear();
console.log("user signed out");
break;
}
});
Hub.listen("datastore", async (data) => {
console.log(data.payload.event, data.payload.data);
if (data.payload.event === "ready") {
setDSReady(true);
}
});
return (
<html>
<body>
<IsDSReadyProvider isDSReady={isDSReady}>
<Authenticator hideSignUp>
{({ user, signOut }) => {
return (
<PostSignIn user={user} signOut={signOut}>
{children}
</PostSignIn>
);
}}
</Authenticator>
</IsDSReadyProvider>
</body>
</html>
);
}
function PostSignIn({
user,
children,
signOut,
}: {
user: AuthUser | undefined;
children: ReactNode;
signOut: any;
}) {
const segment = useSelectedLayoutSegment() || "";
const [attrs, setAttrs] = useState<FetchUserAttributesOutput>();
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<unknown>();
const isDSReady = useContext(IsDSReady);
useEffect(() => {
fetchUser();
(async () => {
await DataStore.start();
})();
}, [user?.userId]);
async function fetchUser() {
try {
setErr(undefined);
setLoading(true);
const attrs = await fetchUserAttributes();
setAttrs(attrs);
} catch (err) {
setErr(err);
} finally {
setLoading(false);
}
}
console.log("isDSReady", isDSReady);
if (loading) {
return (
// code removed
);
}
if (!attrs?.name || !attrs?.email) {
return;
}
if (!user || err) {
console.log(err);
return;
}
const _user = {
userId: user.userId,
name: attrs.name!,
email: attrs.email,
preferred_username: attrs.preferred_username,
};
return (
<UserProvider user={_user}>
<LoadingBar color="#6B83FF" progress={!isDSReady ? 30 : 100} />
<aside>
<button className="inline-flex" onClick={signOut}>
<LogoutIcon fill={"#7E879C"} />
</button>
</aside>
<div>
{children}
</div>
</UserProvider>
);
}
// context IsDSReady
export const IsDSReady = createContext<boolean>(false);
export function IsDSReadyProvider(props: {
isDSReady: boolean;
children: ReactNode;
}) {
return (
<IsDSReady.Provider value={props.isDSReady}>
{props.children}
</IsDSReady.Provider>
);
}
// context UserProvider
export function UserProvider({
user,
children,
}) {
<UserContext.Provider value={user}>
<DispatchContext.Provider value={() => {}}>
{children}
</DispatchContext.Provider>
</UserContext.Provider>
}
// here is another file where we are subscribing as our first view
function Page() {
const orgID = getOrgId(useUser()); // added snippet below
const [reports, setReports] = useState<Report[]>([]);
const router = useRouter();
useEffect(() => {
const sub = DataStore.observeQuery(
Report,
(r) => r.organizationID.eq(orgID),
{
sort: (s) => s.createdAt(SortDirection.DESCENDING),
}
).subscribe(({ items }) => {
setReports(items);
});
return () => sub.unsubscribe();
}, [orgID]);
return <div> </div> // renders the view (code removed)
}
export function useUser() {
const user = useContext(UserContext);
return user!;
}
export const getOrgId = (user) => user.preferred_username;
Thank you for the detail about how your application uses DataStore. I tried to pull together a sample app that would use this code and encountered missing pieces. Would it be possible to share an application the is having this error as a public repo or share a private repo explicitely with @cwomack or myself?
Looking over this code, I have a couple questions:
DataStore.query
is called directly. I have checked and DataStore.observeQuery
calls DataStore.query
, which from the provided context must be where the error is coming from. How does your application ensure that the observeQuery
calls are closed out and not re-activated while the stop
and clear
actions are in flight?Answering questions is helpful, but more than anything getting this error reproduced will help us to provide guidance and fix any underlying issues more directly.
Thanks, Aaron
@stocaaro Thank you Aaron for looking into it. Let me get back to you about sharing the repo. But let me quickly answer some of the questions
use client
Regardless we are unsubscribing as soon as the component unmounts after logging out.
please let me know if something isn't clear above, and let me get back to you about the share.
Hello @mks11 ,
Thanks for the additional input. In my experience useEffect
doesn't provide sequencing guarantees. If it's not possible to control when stop
and clear
call with respect to when observeQuery
calls take place, can we catch these errors when they come up as a workaround?
Have you been able to log/follow event sequencing to ensure that they are occurring in the order you expect?
I would really like to see this error happen in my environment, but I'm missing details about how your app renders components. Things I run into trying to repro: use client
associated with some but not all components, unclear how import and setup happen server vs client context, missing how UserContext
is defined and how it fits into the app. Some of these pieces probably aren't needed for repro, but trying to build an app that works like yours is a challenge without more complete information.
Thanks, Aaron
Hi @stocaaro,
I just shared a Todo version of the app with you, please let me know if you'd need anything from me (not sure if I need to create a user account for you).
Thank you! I'm spinning it up on my own account and will create an account for myself. After logging in, I've done some clicking around and added a button to create todo's to see if that would help me trigger the issue.
Here's a screenshot of what I'm seeing.
I tried logging in and out in quick succession and still haven't triggered the error from above. Do you know what sequence of steps might help me trigger the error?
I really appreciate your work to pull this together!
Regards, Aaron
Thank you @stocaaro, that's strange because I am seeing it even inside a single tab. But I'd like to add that this happens more consistently when you have another tab open, maybe you could try keeping a tab open (logged in), and login/logout in the current tab inside the same window? Thanks again!
That did it. Using multiple tabs, I was able to reproduce the issue you describe.
Reproduction steps:
Observe that the tab you logged out and then back in on hangs. To un-hang this tab, you can close the other tab and refresh.
This happens because the DataStore.clear()
call will only drop the local database (IndexDB) if no other tab is accessing it. Since clear never succeeds, all other datastore processes hang waiting on this to resolve.
This is one of a couple datastore issues happen when multiple tabs are accessing the same local IndexDB instance at the same time.
Related: https://github.com/aws-amplify/amplify-js/issues/7371
Better support for multi-tab/window is on our backlog. I can't find another case that better documents this issue with clear/stop, so I'm going to leave this open marked as a feature request.
The issue isn't specifically related to your observeQuery or query calls, having two windows with DataStore started and running means that a clear call in one of them will await until the other window is closed. I've asked around a bit, but don't have a work around recommendation at this time.
Labeling this as a feature request, as we don't support cross tab signaling out of the box at this point.
Before opening, please confirm:
JavaScript Framework
Next.js
Amplify APIs
DataStore
Amplify Version
v6 "aws-amplify": "^6.0.9", from package.json
Amplify Categories
auth, api
Backend
Amplify CLI
Environment information
Describe the bug
DataStore fails to clear or stop after logging out. This is the screenshot of the error
This is the snippet from a top level layout
Component A
And this the snippet of the code that should kick off DataStore in a component that is rendered after the user has signed up successfully
Component B
fetchUser
is just fetchUserAttributes() from Amplify v6.Expected behavior
The DataStore should clear after user has signout without throwing the error.
Reproduction steps
Code Snippet
Component A
Component B
Log output
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