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

incremental: subsequent result records should not store parent references #3929

Merged
merged 3 commits into from
Jul 6, 2023

Conversation

yaacovCR
Copy link
Contributor

@yaacovCR yaacovCR commented Jul 3, 2023

as memory then cannot be freed

This affects both the existing branching executor on main as well as the non-branching, deduplicated version in #3886

We want to ensure that after an incremental result is sent to the client, no subsequent results reference this result so that garbage collection can free the memory associated with the result. To effect this, two changes are required:

  1. Prior to this change, we performed filtering by modifying the children stored on a parent; now we add a flag on the filtered item itself, so that we no longer need a backreference from child to parent.
  2. We no longer store a permanent reference on the ExecutionContext to the children of the initial result. Rather, we have the IncrementalPublisher provide a new InitialResultRecord that carries its own errors and children properties.

@netlify
Copy link

netlify bot commented Jul 3, 2023

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit cfbe5a4
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/64a563214b800f0008dc6b52
😎 Deploy Preview https://deploy-preview-3929--compassionate-pike-271cb3.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@github-actions
Copy link

github-actions bot commented Jul 3, 2023

Hi @yaacovCR, I'm @github-actions bot happy to help you with this PR 👋

Supported commands

Please post this commands in separate comments and only one per comment:

  • @github-actions run-benchmark - Run benchmark comparing base and merge commits for this PR
  • @github-actions publish-pr-on-npm - Build package from this PR and publish it on NPM

@yaacovCR yaacovCR force-pushed the free-parent branch 3 times, most recently from 0694bab to 8371586 Compare July 3, 2023 12:28
@yaacovCR yaacovCR requested a review from robrichard July 3, 2023 12:38
@yaacovCR yaacovCR added the PR: feature 🚀 requires increase of "minor" version number label Jul 3, 2023
as then memory for the result record tree cannot be freed
@yaacovCR yaacovCR merged commit fae5da5 into graphql:main Jul 6, 2023
21 checks passed
@yaacovCR yaacovCR deleted the free-parent branch July 6, 2023 00:49
yaacovCR added a commit that referenced this pull request Aug 18, 2023
extracted from #3929

the publisher itself can determine whether to return a single result or
the initial result + stream

the only desired change is to replace the following code block with the
below:


[FROM:](https://github.com/graphql/graphql-js/blob/fae5da500bad94c39a7ecd77a4c4361b58d6d2da/src/execution/execute.ts#L293-L340)

```ts
  const incrementalPublisher = exeContext.incrementalPublisher;
  const initialResultRecord = incrementalPublisher.prepareInitialResultRecord();
  try {
    const result = executeOperation(exeContext, initialResultRecord);
    if (isPromise(result)) {
      return result.then(
        (data) => {
          const errors =
            incrementalPublisher.getInitialErrors(initialResultRecord);
          const initialResult = buildResponse(data, errors);
          incrementalPublisher.publishInitial(initialResultRecord);
          if (incrementalPublisher.hasNext()) {
            return {
              initialResult: {
                ...initialResult,
                hasNext: true,
              },
              subsequentResults: incrementalPublisher.subscribe(),
            };
          }
          return initialResult;
        },
        (error) => {
          incrementalPublisher.addFieldError(initialResultRecord, error);
          const errors =
            incrementalPublisher.getInitialErrors(initialResultRecord);
          return buildResponse(null, errors);
        },
      );
    }
    const initialResult = buildResponse(result, initialResultRecord.errors);
    incrementalPublisher.publishInitial(initialResultRecord);
    if (incrementalPublisher.hasNext()) {
      return {
        initialResult: {
          ...initialResult,
          hasNext: true,
        },
        subsequentResults: incrementalPublisher.subscribe(),
      };
    }
    return initialResult;
  } catch (error) {
    incrementalPublisher.addFieldError(initialResultRecord, error);
    const errors = incrementalPublisher.getInitialErrors(initialResultRecord);
    return buildResponse(null, errors);
  }
}
```


[TO:](https://github.com/yaacovCR/graphql-executor/blob/598608e8d8b23bc527dd73283b477997305afd58/src/execution/execute.ts#L234-L250):

```ts
  const incrementalPublisher = exeContext.incrementalPublisher;
  const initialResultRecord = incrementalPublisher.prepareInitialResultRecord();
  try {
    const data = executeOperation(exeContext, initialResultRecord);
    if (isPromise(data)) {
      return data.then(
        (resolved) =>
          incrementalPublisher.buildDataResponse(initialResultRecord, resolved),
        (error) =>
          incrementalPublisher.buildErrorResponse(initialResultRecord, error),
      );
    }
    return incrementalPublisher.buildDataResponse(initialResultRecord, data);
  } catch (error) {
    return incrementalPublisher.buildErrorResponse(initialResultRecord, error);
  }
```

Supporting changes are required:
1. some existing public methods no longer are required to be public and
so are made private (or removed entirely!), with lint rules forcing the
reordering of existing methods
2. to prevent cyclic type dependencies (not strictly necessary, but
still!), types are moved from `execute.ts` to `IncrementalPublisher.ts`
sakesun pushed a commit to sakesun/graphql-js that referenced this pull request Sep 1, 2023
extracted from graphql#3929

the publisher itself can determine whether to return a single result or
the initial result + stream

the only desired change is to replace the following code block with the
below:


[FROM:](https://github.com/graphql/graphql-js/blob/fae5da500bad94c39a7ecd77a4c4361b58d6d2da/src/execution/execute.ts#L293-L340)

```ts
  const incrementalPublisher = exeContext.incrementalPublisher;
  const initialResultRecord = incrementalPublisher.prepareInitialResultRecord();
  try {
    const result = executeOperation(exeContext, initialResultRecord);
    if (isPromise(result)) {
      return result.then(
        (data) => {
          const errors =
            incrementalPublisher.getInitialErrors(initialResultRecord);
          const initialResult = buildResponse(data, errors);
          incrementalPublisher.publishInitial(initialResultRecord);
          if (incrementalPublisher.hasNext()) {
            return {
              initialResult: {
                ...initialResult,
                hasNext: true,
              },
              subsequentResults: incrementalPublisher.subscribe(),
            };
          }
          return initialResult;
        },
        (error) => {
          incrementalPublisher.addFieldError(initialResultRecord, error);
          const errors =
            incrementalPublisher.getInitialErrors(initialResultRecord);
          return buildResponse(null, errors);
        },
      );
    }
    const initialResult = buildResponse(result, initialResultRecord.errors);
    incrementalPublisher.publishInitial(initialResultRecord);
    if (incrementalPublisher.hasNext()) {
      return {
        initialResult: {
          ...initialResult,
          hasNext: true,
        },
        subsequentResults: incrementalPublisher.subscribe(),
      };
    }
    return initialResult;
  } catch (error) {
    incrementalPublisher.addFieldError(initialResultRecord, error);
    const errors = incrementalPublisher.getInitialErrors(initialResultRecord);
    return buildResponse(null, errors);
  }
}
```


[TO:](https://github.com/yaacovCR/graphql-executor/blob/598608e8d8b23bc527dd73283b477997305afd58/src/execution/execute.ts#L234-L250):

```ts
  const incrementalPublisher = exeContext.incrementalPublisher;
  const initialResultRecord = incrementalPublisher.prepareInitialResultRecord();
  try {
    const data = executeOperation(exeContext, initialResultRecord);
    if (isPromise(data)) {
      return data.then(
        (resolved) =>
          incrementalPublisher.buildDataResponse(initialResultRecord, resolved),
        (error) =>
          incrementalPublisher.buildErrorResponse(initialResultRecord, error),
      );
    }
    return incrementalPublisher.buildDataResponse(initialResultRecord, data);
  } catch (error) {
    return incrementalPublisher.buildErrorResponse(initialResultRecord, error);
  }
```

Supporting changes are required:
1. some existing public methods no longer are required to be public and
so are made private (or removed entirely!), with lint rules forcing the
reordering of existing methods
2. to prevent cyclic type dependencies (not strictly necessary, but
still!), types are moved from `execute.ts` to `IncrementalPublisher.ts`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: feature 🚀 requires increase of "minor" version number
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants