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

introduce new Publisher for incremental delivery #3786

Closed
wants to merge 3 commits into from

Conversation

yaacovCR
Copy link
Contributor

Depends on #3784

The proposed new Publisher:

  1. does not use the event loop to manage AsyncRecord dependencies
  2. uses separate sets to store pending vs ready AsyncRecords
  3. does not use Promise.race

No event loop for managing AsyncRecord dependencies

The current Publisher wraps every AsyncRecord result in a promise that only resolves after the parent AsyncRecord resolves. If multiple items within a stream are released for publishing because their parent has just been published, the stream cannot be in its entirety until after all of these promises unwind.

The new Publisher keeps track of dependencies manually. When an AsyncRecord is pushed by the publisher, all of its dependent AsyncRecords are synchronously pushed, repeating as necessary, without using the event loop.

Separate sets for pending vs ready AsyncRecords

The current publisher inspects all pending AsyncRecords whenever any of them resolves. All that are completed are added to the response. The new Publisher moves AsyncRecords from the pending set to the ready set as they are pushed, so that on each call to next, only the ready set must be iterated.

As a side-effect of this change, the incremental array is ordered by which items are ready for delivery first, and not by the initial document.
This seems like a worthwhile tradeoff, and is still adherent to the spec, as far as I can tell.

No Promise.race

The old Publisher uses Promise.race as the trigger to determine whether payloads are ready. The new Publisher uses a single triggering promise that is triggered whenever an AsyncRecord is pushed, and then reset. This may be beneficial as the implementation of Promise.race within V8 has a known memory leak for long-running promises. (see https://bugs.chromium.org/p/v8/issues/detail?id=9858). An alternative would be to utilize @brainkim 's memory-safe version detailed in that issue.

@netlify
Copy link

netlify bot commented Nov 24, 2022

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit 9feafd8
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/63b6a66a97081100081f255e
😎 Deploy Preview https://deploy-preview-3786--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 settings.

@github-actions
Copy link

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
Copy link
Contributor Author

yaacovCR commented Nov 25, 2022

This PR depends on #3784 which extracts out the current publisher from the execution workflow without changing any tests.

The final commit is the single commit unique to this PR that swaps out the old publisher for the new and demonstrates how the executor uses it differently and how batching of incremental payloads change.

  1. How does publisher use by the executor change with the new publisher?

Currently, the executor adds a promise field to AsyncPayloadRecords containing a promise that will resolve when the payload is ready. The promise would not resolve unless the AsyncPayloadRecord's parent was also ready, using the event queue to manage the hierarchy.

The new Publisher introduces a new complete method. The executor calls this method when appropriate to signal to the Publisher than an AsyncPayloadRecord is ready. The Publisher itself manages the parent hierarchy. The complete method checks to see whether an AsyncPayloadRecord can be pushed, and puts it on waiting queue as necessary; when the parent is pushed, the waiting queues are synchronously drained as possible.

  1. How do tests change with the new publisher?

As a result of the fact that an AsyncPayloadRecords can be completed synchronously, there are often fewer distinct payloads. In these cases, the incremental list within each payload contains a greater number of AsyncPayloadRecords.

yaacovCR added a commit to yaacovCR/graphql-spec that referenced this pull request Nov 27, 2022
yaacovCR added a commit to yaacovCR/graphql-spec that referenced this pull request Nov 27, 2022
@yaacovCR
Copy link
Contributor Author

This PR corresponds to a proposed PR on the existing "incremental delivery" spec PR:

See robrichard/graphql-spec#9

It turns out that the existing spec PR have introduced this asynchronous chaining into the spec as well. The associated spec PR follows the new implementation in removing it.

@yaacovCR yaacovCR force-pushed the new-pub branch 2 times, most recently from 4da741c to 61260af Compare November 27, 2022 19:00
yaacovCR added a commit to yaacovCR/graphql-spec that referenced this pull request Nov 27, 2022
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Jan 3, 2023
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Jan 3, 2023
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Jan 3, 2023
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Jan 3, 2023
Depends on graphql#3784

The proposed new Publisher:

1. does not use the event loop to manage AsyncRecord dependencies
2. performs as much work as possible synchronously
2. uses separate sets to store pending vs ready AsyncRecords
3. does not use `Promise.race`

-- No event loop for managing AsyncRecord dependencies

The current Publisher wraps every AsyncRecord result in a promise that only resolves after the parent AsyncRecord resolves. If multiple items within a stream are released for publishing because their parent has just been published, the stream cannot be in its entirety until after all of these promises unwind.

The new Publisher keeps track of dependencies manually. When an AsyncRecord is pushed by the publisher, all of its dependent AsyncRecords are synchronously pushed, repeating as necessary, without using the event loop.

In general, the new publisher aims to perform as much work as possible synchronously.

-- Separate sets for pending vs ready AsyncRecords

The current publisher inspects all pending AsyncRecords whenever any of them resolves. All that are completed are added to the response. The new Publisher moves AsyncRecords from the pending set to the ready set as they are pushed, so that on each call to next, only the ready set must be iterated.

As a side-effect of this change, the incremental array is ordered by which items are ready for delivery first, and not by the initial document.
 This seems like a worthwhile tradeoff, and is still adherent to the spec, as far as I can tell.

-- No `Promise.race`

The old Publisher uses `Promise.race` as the trigger to determine whether payloads are ready. The new Publisher uses a single triggering promise that is triggered whenever an AsyncRecord is pushed, and then reset. This may be beneficial as the implementation of `Promise.race` within V8 has a known memory leak for long-running promises. (see https://bugs.chromium.org/p/v8/issues/detail?id=9858). An alternative would be to utilize @brainkim 's memory-safe version detailed in that issue.
@yaacovCR
Copy link
Contributor Author

yaacovCR commented Jan 5, 2023

Failing tests for this PR after rebasing on main appear to have to do with bug identified in #3815. Apparently, the new Publisher is able to surface this issue with the current test suite after #3754 landed.

#3815 will need to be solved separately in main, and presumably this PR can be rebased on top of that.

@yaacovCR
Copy link
Contributor Author

yaacovCR commented Mar 20, 2024

this was reworked and merged as #3894

@yaacovCR yaacovCR closed this Mar 20, 2024
@yaacovCR yaacovCR deleted the new-pub branch March 20, 2024 09:52
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.

1 participant