Skip to content
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

useLazyQuery calls for the same endpoint in quick succession results in at least one of the requests being canceled #10717

Closed
rodericktech opened this issue Apr 3, 2023 · 17 comments
Labels
🔍 investigate Investigate further

Comments

@rodericktech
Copy link

rodericktech commented Apr 3, 2023

Issue Description

Beginning with 3.7.11, I am now seeing a behavior in our React front end on a complex page where calls to the same endpoint in succession (with different parameters / variables) result in at least one of the queries being canceled (along with other strange behavior).

I do not know how to put enough code together to provide a guaranteed working example close to my situation as the use case is rather complicated and I have only seen the breakage in one place in the front end so far. (And the code I'm writing / company this is for has everything under NDA, so the project cannot be shared directly or pointed at.)

To summarize the behavior: a page that is loading related data sets, looking at the data from multiple 'angles' (and so using multiple queries), has two Apollo queries in succession with the same name / same endpoint in the underlying API. One of the queries is designed to retrieve detailed information about a single entity; the second is run to make a shallow list of identifying information for all the similar entities in that 'stack'. (This is for performance reasons, as the query returning more detailed data is much slower for the entities in question.)

Both of these operations run useLazyQuery under the hood with the same-named query (call it query ListTheseThings() ...), just passing in different variables. The first query appears to run, but when the second (shallow) query is attempted, app execution dies immediately even though a response to that query does show in dev tools; the first query then appears to be 'canceled'. (According to the network tab.)

Both queries pass 'no-cache' so caching is not involved.

After reverting to 3.7.10 the queries both execute as expected and the page render completes normally.

Link to Reproduction

https://www.cant-create-a-repro-for-this.test/im-sorry

Reproduction Steps

No response

@jerelmiller
Copy link
Member

jerelmiller commented Apr 3, 2023

Hey @rodericktech 👋

Apologies this is happening with the latest update. I recently fixed several bugs where 1) multiple calls to execution function in a row would resolve with data from the last execution rather than the request it kicked off and 2) would ensure the promise isn't stuck pending indefinitely when a component unmounts.

It's odd to me that you're seeing any kind of cancellation given that I removed the abort related code between v3.7.10 and v3.7.11. As you can see, we've also tested to ensure multiple queries with differing variables works correctly. Am I understanding that the "cancellation" in this case is that the network request is aborted?

I totally understand about the NDA issue. That said, I could really use some more information to understand what is happening here, as I'm not sure I have enough to go on to be able to reproduce the issue. I'm hoping you can provide me with some code samples with obscured names/details and/or more information to understand the conditions under which this issue is happening. Some information that would be helpful:

  • What fetchPolicy are you using? Do you see this behavior with a particular fetch policy that you don't with others? Edit: I see you're using no-cache, I missed that in my initial read-through.
  • Are you using a single useLazyQuery function and calling the execution function multiple times with different variables? Or are you using multiple useLazyQuery hooks with the same query document?
  • Do you use any kind of abort signal in your link chain and/or context passed to useLazyQuery options? What other kinds of options do you pass useLazyQuery?
  • Does one of the promises returned by the execution function reject with a particular error when the request is cancelled?

Alternatively, if you are able to write me a failing test, this would go a long way to helping me out. We shouldn't need your schema or your query names to recreate the issue. Trying to distill down the relevant info (same query name, different vars, etc) can be extremely helpful to narrow this down. Thanks!

@jerelmiller jerelmiller added 🔍 investigate Investigate further 🏓 awaiting-contributor-response requires input from a contributor labels Apr 3, 2023
@rodericktech
Copy link
Author

Thank you for your prompt reply @jerelmiller !

I do not know how soon I can make time to try to create an approximation of the code situation or a failing test but in the meantime I will do my best to answer your questions.

  1. I am using a single useLazyQuery function (accessed via the same single hook) and calling it twice with different variables.
  2. There is no abort signal being passed; the only options passed are the query itself (with variables) and a global (simple) error handler being passed to onError. (All the error handler does is attempt to parse errors that come back from the API and then display relevant text - nothing fancy at this stage of development.)
  3. I have not actually seen any Promise fail, nor any error of any kind reported either in the local JS context or from the API, but I will try again and see if I can get something to produce some kind of feedback. The only observable difference I found so far was the cancellation reported in the network tab.

The query looks something like this:

export const LIST_THESE_THINGS = gql`
  query ListTheseThings(
    $nameOfThing: input_filter
    $limit: Int
    $offset: Int
  ) {
    subset {
      listTheseThings(nameOfThing: $nameOfThing, _limit: $limit, _offset: $offset) {
        _param1
        id
        instances {
          id
          nameOfThing
          param2
          param3
          setOfStuff {
            id,
            param4,
          }
        }
      }
    }
  }
`;

It is called with useLazyQuery:

export const useListTheseThingsQuery = () => {
  const { handleError } = useHandleErrors();

  const [retrieveTheseThingsData, state] = useLazyQuery<
    ListTheseThingsData,
    ListTheseThingsVars
  >(LIST_THESE_THINGS, {
    onError: handleError,
  });

  const listTheseThings = async ({
    pageNumber,
    pageSize,
    sortBy,
    sortDescending,
    searchTerms,
  }: UseListTheseThingsProps = {}): Promise<SomeKindOfResults[]> => {
    const variables: ListTheseThingsVars = {
      ...(pageNumber &&
        pageSize && {
        ...getPaginationParamsForAQuery(pageNumber, pageSize),
      }),
      ...(searchTerms && { name: { value: searchTerms, op: '$in' } }),
    };

    const theseThingsResponse = await retrieveTheseThingsData({
      variables,
      fetchPolicy: 'no-cache',
    });
    
    ...
    
    // do things with data and return stuff

The function in the hook (listTheseThings()) is called twice in succession with different parameters passed to it in each case. Execution of the app just dies the second time it runs await retrieveTheseThingsData() with no visible error reports from anywhere. The first time through it appears to be fine. I hope this is at least a good starting point.

@jerelmiller
Copy link
Member

Thanks so much @rodericktech! The code samples help a ton.

Just to make sure I'm understanding whats being called here, are you calling your wrapped function twice in succession, or are you calling retrieveTheseThingsData twice in your wrapped function?

If the wrapped function, are you running those in parallel or awaiting the results of one, then kicking off the other?

// in parallel
const [result1, result2] = await Promise.all([
  listTheseThings(...), 
  listTheseThings(...)
]);

// or awaiting one then kicking off the other?
const result1 = await listTheseThings(...);
const result2 = await listTheseThings(...);

Just want to make sure I can accurately try and reproduce this as much as possible. Thanks!

@rodericktech
Copy link
Author

You're welcome! In my case, I'm calling the wrapped function twice in succession, along the lines of the latter of your two examples - awaiting the result of the first and then starting the second shortly afterward.

@github-actions github-actions bot removed the 🏓 awaiting-contributor-response requires input from a contributor label Apr 4, 2023
@jerelmiller
Copy link
Member

Awesome thank you! I'll try and setup a toy app that tries to reproduce this and see if I can get the same result.

@dobrynin
Copy link

fwiw we are seeing the same behavior. Reverting to 3.7.10 did not seem to solve it for us.

@dobrynin
Copy link

It actually looks like updating to the latest Apollo Client makes the issue go away, although it's hard to be sure because the issue is intermittent.

@rodericktech
Copy link
Author

rodericktech commented May 10, 2023

I have tried 3.7.14 and the issue is still present for me, though now I am caught in a difficult spot. So far I have been able to work on this project after reverting to version 3.7.10, but I am now encountering this issue in another place in the application: #10520

In versions 3.7.11 and later, that issue (10520) does appear to be fixed for me, but the issue in the current thread re-appears.

@jerelmiller
Copy link
Member

@rodericktech would you be able to revert to v3.7.3 or earlier? v3.7.4 introduced the behavior you're seeing in #10520. I'm not sure if there are other bug fixes you're relying on in later patch versions, but this might be the quickest fix if not.

Apologies I haven't had time to get back to this! Its on my list of things to come back to as soon as I'm finished up with some of my current commits.

@rodericktech
Copy link
Author

@jerelmiller No problem - I understand the kinds of constraints involved in managing a project like this. I will try 3.7.3 and see if that is a workable temporary solution - thank you.

@rodericktech
Copy link
Author

Just an update to say I've kept our project on 3.7.3 and so far it's still working, but wanted to see if this issue might have been addressed in any of the more recent updates.

@jerelmiller
Copy link
Member

Hey @rodericktech, I know its been a while since you opened this issue and I'm sorry its taken me this long to get back to you.

We just released a fix in 3.9.4 that addressed an issue with double network calls due to some weird variables madness that resulted in the query being executed twice (once with incorrect variables, another with correct variables).

Reading back through this thread, I suspect the cancelled request might be because of this weird duplicate request situation where we inadvertently sent the same request when calling execute again (with the wrong variables), so the 2nd request ended up cancelling the first.

Would you mind trying 3.9.4 to see if the issue still persists?

@rodericktech
Copy link
Author

Thank you for getting back to me @jerelmiller - I will try 3.9.4 as soon as I can and report back to you.

@rodericktech
Copy link
Author

Hello again - thus far in my testing with 3.9.4, I have not been able to replicate the former issue in the places where I observed it. Hopefully this means it's resolved :) Thank you!

@jerelmiller
Copy link
Member

@rodericktech thats wonderful news! As such, I'll go ahead and close this issue 🙂

If you wanted to be absolutely sure, downgrading to 3.9.3 should reproduce the issue (no need to do that unless you're bored 🤣)

Thanks for all the patience on this one!

Copy link
Contributor

github-actions bot commented Feb 8, 2024

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🔍 investigate Investigate further
Projects
None yet
Development

No branches or pull requests

3 participants