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

extract out publisher #3784

Closed
wants to merge 2 commits into from
Closed

Conversation

yaacovCR
Copy link
Contributor

@yaacovCR yaacovCR commented Nov 22, 2022

Motivation:

  1. separation of concerns
  2. creating a base for publisher refactoring

To elaborate on number 2:

Currently, emitting a stream utilizes the event loop to ensure that all stream items are emitted in order, with an extra microtask for each stream item. Utilizing the event loop in this way will be slower than doing so synchronously -- for cases that allow the latter.

In other words, if a late completing early stream item completes and many later stream items are then available for publishing; we currently waterfall through promise resolution for each stream item, rather than adding all the now available incremental results.

Extracting out our current publisher logic allows us to iterate on a proposal that potential has feature improvements such as a resolution of the above potential performance issue.

@netlify
Copy link

netlify bot commented Nov 22, 2022

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit c537db4
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/63b6a3905131c40009ca3e6c
😎 Deploy Preview https://deploy-preview-3784--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 yaacovCR requested review from robrichard and a team November 22, 2022 20:05
@yaacovCR yaacovCR added the PR: polish 💅 PR doesn't change public API or any observed behaviour label Nov 22, 2022
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 24, 2022
depends on graphql#3784

The proposed new publisher does not use the event loop to manage AsyncRecord dependencies => and so if multiple items within a stream are released from publishing because their parent has just been published, they are all released at once.

Another difference is that different sets are used to store the AsyncRecords that are pending vs ready for publishing, etc. This provides a performance benefit in that on a call to next, the set of all AsyncRecords is not inspected. 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.

The subscribe algorithm used does not use Promise.race -- this may also 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)
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 25, 2022
depends on graphql#3784

The proposed new publisher does not use the event loop to manage AsyncRecord dependencies => and so if multiple items within a stream are released from publishing because their parent has just been published, they are all released at once.

Another difference is that different sets are used to store the AsyncRecords that are pending vs ready for publishing, etc. This provides a performance benefit in that on a call to next, the set of all AsyncRecords is not inspected. 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.

The subscribe algorithm used does not use Promise.race -- this may also 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)
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 25, 2022
depends on graphql#3784

The proposed new publisher does not use the event loop to manage AsyncRecord dependencies => and so if multiple items within a stream are released from publishing because their parent has just been published, they are all released at once.

Another difference is that different sets are used to store the AsyncRecords that are pending vs ready for publishing, etc. This provides a performance benefit in that on a call to next, the set of all AsyncRecords is not inspected. 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.

The subscribe algorithm used does not use Promise.race -- this may also 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)
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 25, 2022
depends on graphql#3784

The proposed new publisher does not use the event loop to manage AsyncRecord dependencies => and so if multiple items within a stream are released from publishing because their parent has just been published, they are all released at once.

Another difference is that different sets are used to store the AsyncRecords that are pending vs ready for publishing, etc. This provides a performance benefit in that on a call to next, the set of all AsyncRecords is not inspected. 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.

The subscribe algorithm used does not use Promise.race -- this may also 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)
@yaacovCR
Copy link
Contributor Author

Now depends on #3787 which was extracted from #3786

yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 25, 2022
Depends on graphql#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.
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#8

yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 27, 2022
Depends on graphql#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.
yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Nov 27, 2022
Depends on graphql#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.
@yaacovCR yaacovCR force-pushed the extract-publisher branch 3 times, most recently from 064a5e9 to f92d86d Compare January 3, 2023 14:49
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. 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.
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. 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.
@yaacovCR
Copy link
Contributor Author

yaacovCR commented Jan 5, 2023

This has been rebased to longer include #3787. (The change within #3787 has been re-integrated into the follow-up to this PR, #3786.)

yaacovCR added a commit to yaacovCR/graphql-js that referenced this pull request Jan 5, 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

this was reworked and merged as #3903

@yaacovCR yaacovCR closed this Mar 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: polish 💅 PR doesn't change public API or any observed behaviour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant