samdenty/gqless

Race condition in SSG (with possible solution)

wjohnsto opened this issue · 1 comments

I hit an issue when doing SSG in a Next.js app where my cached page data is not always complete. I'm using prepareReactRender, and the temporary solution that seemed to work was to call prepareReactRender twice. This indicates to me that the underlying issue is a race condition.

After doing some debugging I believe I have identified the area of code that has the race condition, and I have a solution for it. The race condition can be found in the ssr.ts file in gqless core. Here is the code in question:

    let renderPromise: Promise<unknown> | unknown | undefined;

    const interceptor = innerState.interceptorManager.createInterceptor();
    let prevIgnoreCache = innerState.allowCache;
    try {
      innerState.allowCache = false;
      renderPromise = render();
    } finally {
      innerState.interceptorManager.removeInterceptor(interceptor);
      innerState.allowCache = prevIgnoreCache;
    }

    await Promise.all([
      renderPromise,
      ...innerState.scheduler.pendingSelectionsGroupsPromises.values(),
    ]);

You can see in the code above that render() returns a promise that is resolved in the Promise.all. Note that this occurs after innerState.interceptorManager.removeInterceptor(interceptor);. However, when you call removeInterceptor the interceptor will no longer receive any new selections. So later on in that method when you look at interceptor.fetchSelections it will only contain the selections that were able to be observed between the time you call render() and removeInterceptor. The following code appears to fix the issue:

    const interceptor = innerState.interceptorManager.createInterceptor();
    let prevIgnoreCache = innerState.allowCache;
    try {
      innerState.allowCache = false;
      await render();
    } finally {
      innerState.interceptorManager.removeInterceptor(interceptor);
      innerState.allowCache = prevIgnoreCache;
    }

    await Promise.all([
      ...innerState.scheduler.pendingSelectionsGroupsPromises.values(),
    ]);

I'm willing to submit a PR to fix this if necessary, just let me know.

thank you @wjohnsto, this fix has been added and is going to be shipped in the new home for gqless, gqty:

https://gqty.dev/

https://github.com/gqty-dev/gqty