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

Allow execution to be cancelled #3791

Closed
wants to merge 18 commits into from
Closed

Conversation

igrlk
Copy link

@igrlk igrlk commented Dec 1, 2022

Hey everyone 👋

This PR addresses #3764

How it works

It extends ExecutionArgs with signal?: AbortSignal.
Then, during the execution, we check whether signal is present and aborted. If that's the case, we throw an error and fail the further execution of resolvers.

Testing

To test it with a real graphql server (and to possibly give an implementation reference to the apollo-server team), I've made a change to Mercurius so that it passes abortSignal to the graphql.experimentExecuteIncrementally and aborts the signal if request connection is closed.

I've also created a minimal reproducible repo that you can clone and test the execution cancelling on your machine 🔥

Feedback

Please let me know if that change makes sense to you.

@glasser that would be awesome if you can play around with apollo-server to see if this way of execution cancelling can be used there :) I deployed this PR to npm and you can install it by npm install --save graphql@canary-pr-3791

@mcollina, I would love to hear your thoughts too!

Cheers! 🎩

@netlify
Copy link

netlify bot commented Dec 1, 2022

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit e781bbc
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/651bbac97de5e80008e89d05
😎 Deploy Preview https://deploy-preview-3791--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 Dec 1, 2022

Hi @igrlk, 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

@igrlk

This comment has been minimized.

@igrlk

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Dec 1, 2022

@github-actions publish-pr-on-npm

@igrlk The latest changes of this PR are available on NPM as
graphql@17.0.0-alpha.2.canary.pr.3791.264f22163eb937ff87a420be9f7d45965f2cbf07
Note: no gurantees provided so please use your own discretion.

Also you can depend on latest version built from this PR:
npm install --save graphql@canary-pr-3791

@igrlk igrlk changed the title accept abortSignal in execute method and check if signal is aborted w… Allow execution to be cancelled Dec 1, 2022
@igrlk igrlk marked this pull request as ready for review December 1, 2022 10:06
@github-actions
Copy link

github-actions bot commented Dec 1, 2022

@github-actions run-benchmark

@igrlk Something went wrong, please check log.

@igrlk

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Dec 1, 2022

@github-actions run-benchmark

@igrlk Please, see benchmark results here: https://github.com/graphql/graphql-js/actions/runs/3594181152/jobs/6052115992#step:6:1

Copy link
Contributor

@glasser glasser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not enough of an execution expert to know if the place where you're checking the signal is the best spot, but I'm really excited to see this! Use cases include:

  • Stopping execution when the client closes their request.
  • Stopping execution after a request timeout.
  • Stopping execution when it evaluates too many fields.

I do have one suggestion! There's an issue I've know about in graphql-js for a while which apparently I never filed as a bug, so I just did that: #3792

If we're going to add this AbortSignal support to graphql-js, I'd suggest doing something slightly differently, where graphql-js always creates a new AbortController which it passes to execution the way to do here. If a signal is passed in then you do passedInSignal.addEventListener('abort', () => abortController.abort(passedInSignal.reason)). Finally, when execution has finished and there's a complete response to be passed back (the same places you're unsubscribing) it can abort the controller it created. This means that if there's any pieces of execution doing work that it doesn't know contributes to an already-rejected Promise.all, that work will stop recursing.

Another thought that doesn't go on a particular line: as a potential extension which doesn't need to be part of this PR, the signal could be made available to resolvers on GraphQLResolveInfo, so that resolvers can themselves abort their work when the operation is aborted.

src/execution/execute.ts Outdated Show resolved Hide resolved
function subscribeToAbortSignal(exeContext: ExecutionContext): {
unsubscribeFromAbortSignal: () => void;
} {
const onAbort = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you start the function with something like

const { signal } = exeContext;
if (!signal) {
  return () => {};
}

does it get a bit easier to read (no more ?., can access signal directly within onAbort without a conditional)?

@@ -832,6 +867,14 @@ function completeValue(
result: unknown,
asyncPayloadRecord?: AsyncPayloadRecord,
): PromiseOrValue<unknown> {
if (exeContext.signal?.isAborted) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My instinct is that this is a global enough thing that if it fires, the overall response should be { data: null, errors: [ { message: "aborted" } ]} rather than keeping whatever data and errors were already produced. (Maybe that's not a change to this code but just a suggestion that at the top level of executeImpl it checks to see if it's aborted and if so replaces the whole response with a short one.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IvanGoncharov @yaacovCR any instincts on this — does it make more sense for "execution stopped due to AbortSignal" to return a partial result with field errors for all resolvers that run after abortion, or just a short error with no data?

function subscribeToAbortSignal(exeContext: ExecutionContext): {
unsubscribeFromAbortSignal: () => void;
} {
const onAbort = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, I don't think you need any of this. The AbortSignal already has an aborted boolean property. Since all we're doing is checking whether things are aborted inside completeValue, that can just check aborted directly, with no subscription needed. (Though if you take my suggestion from the top level of the review to always have an AbortController and hook up the user-provided one to it, then you'll still need the structure of subscribe/unsubscribe — it just won't be updating a boolean.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to go with the option you suggested and create a new AbortController for each execution

@@ -261,6 +265,7 @@ export interface ExecutionArgs {
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
signal?: AbortSignal;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExecutionArgs is also the argument to subscribe/experimentalSubscribeIncrementally. So this PR allows you to specify signal to subscription APIs. I would expect that if I passed signal to a subscription API and then I aborted the signal at some point in the middle of a subscription (say, between source events), that it would also complete the subscription somehow, but I don't think your implementation does this.

Personally I think it would be reasonable for this PR to only work with non-subscription execution (and subscription support could be added later) — so my suggestion is for experimentalSubscribeIncrementally to throw if signal is provided, unless you're familiar with subscriptions and are motivated to make it work there too!

Copy link
Author

@igrlk igrlk Dec 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be happy to try implementing it for Subscriptions but I think it makes sense to reduce this PR scope to minimal.

I added error throwing in case of a client tries to pass an abort signal to subscriptions execution, thanks for the suggestion!

@@ -261,6 +265,7 @@ export interface ExecutionArgs {
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
signal?: AbortSignal;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be reasonable to add this to the top-level GraphQLArgs/graphql function as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite sure I understand the idea, can you explain why?

The way I understand it: clients (graphql servers) are calling execute/experimentalExecuteIncrementally and that's exactly the place where they will be passing an abort signal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a function called graphql that you can use that wraps a bunch of stuff (parsing, validation, execution) into a single function call. So it might be reasonable for users of that API to be able to set a signal (it's just a matter of adding it to GraphQLArgs and threading it through to execute).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I added signal to the GraphQLArgs/graphql call

@@ -832,6 +867,14 @@ function completeValue(
result: unknown,
asyncPayloadRecord?: AsyncPayloadRecord,
): PromiseOrValue<unknown> {
if (exeContext.signal?.isAborted) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, also: I think completeValue is probably the wrong place to put this, or at least if you do put this here you need to be a bit more careful. Because if result is an array of Promises, now you are "leaking" the Promises by not calling catch or putting them in a Promise.all or whatever, which seems incorrect... it's generally a bad idea to have unhandled Promises and can lead to weird logging and such.

I don't know the best place to put this check (and maybe it should go in multiple places) but putting it at the top of executeField (ie, before doing a resolver call, not midway through after) seems reasonable?

(Or you could leave it here but also be careful to find any Promises in result and put an empty catch on them or something...)

@glasser
Copy link
Contributor

glasser commented Dec 2, 2022

There's some CI failures. The integration test failures indicate that you probably either need to require some @types/node in packages that depend on graphql, or you can get the TS AbortSignal from some other package.

One of my suggestions involves the need to actually new AbortController() at runtime too, which raises the question of what implementation to use from being a TS issue to a runtime issue. I believe we need graphql-js (even execution, right?) to work in browsers and other non-Node environments, though I don't know how old the browsers we need to support are (and I don't know if we need to support environments other than browsers and Node). But in any case we still support Node v14 and the AbortController and AbortSignal classes aren't available as globals (without an experimental CLI flag) until v15, so you need some sort of polyfill anyway... so might as well use the polyfill for TS as well.

In Apollo Server we've been happy with https://www.npmjs.com/package/node-abort-controller but that's Node-specific; https://www.npmjs.com/package/abort-controller is fully portable I think.

Although I guess graphql-js has zero dependencies (is that an absolute requirement @IvanGoncharov ?) In that case the choices (assuming you like my suggestion that graphql-js should use its own AbortController to resolve #3792) are to only fix the #3792 bug in Node 16 and newer, or to copy a polyfill of AbortController into graphql-js.

@yaacovCR
Copy link
Contributor

yaacovCR commented Dec 4, 2022

Second that this is very exciting. My thoughts around adding an abort mechanism were more centered on #3792 ie not receiving an abortsignal, but rather passing it into resolvers.

I assumed that we would end up needing to create a separate AbortController for every nullable field, so that we could abort resolvers as soon as a null bubbled up. I hadn't thought of creating a single AbortController for everything and simply aborting after the result has been returned, ie @glasser's suggestion above. The down side is that we end up having to wait to cancel anything until we get the final result, while the upside is that it's easier to manage and may have a performance boost in the non-error case.

Following along here with interest!

@glasser
Copy link
Contributor

glasser commented Dec 4, 2022

@yaacovCR yeah I think we could do a more complex thing to manage extraneous execution more precisely (perhaps by moving away from Promise.all to something more explicit) but that could be an iterative improvement later.

@IvanGoncharov IvanGoncharov added the PR: feature 🚀 requires increase of "minor" version number label Dec 5, 2022
@igrlk
Copy link
Author

igrlk commented Dec 7, 2022

There's some CI failures

@glasser, I've added dummy AbortSignal class in case of Node@14 - tests pass now!

}),
resolve: () =>
new Promise((resolve) => {
setTimeout(() => resolve({}), WAIT_MS_BEFORE_RESOLVING);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I don't see any other uses of setTimeout in the test suite. Is it possible to avoid setTimeout and just have some Promises created in the test that are manually resolved or not resolved as part of the test? setTimeout-based tests can get flaky under load...

class DummyAbortController implements IAbortController {
private _signal: IAbortSignal = {
aborted: false,
addEventListener: () => null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the reason that this is valid is because we don't actually provide the AbortSignal to any code other than the code in graphql-js itself, which doesn't actually call addEventListener at all. If we're going to do that then we might as well go back to something more similar to your original code where we listen on the incoming AbortSignal to update some boolean, and then we also update that boolean when execution is done, rather than have an AbortSignal that doesn't actually support this important method... though I would honestly prefer a system where the AbortSignal is real so it can be provided to resolvers (which requires a more complete polyfill, or the concept that this particular feature only works if you're in a JS environment with a global AbortController). @IvanGoncharov thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any plans on dropping support for node@14? Would be cool to track it down somewhere so that people know when it's going to happen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if graphql-js has any particular plans, but Node v14 does go out of LTS in April. (I'm not sure what non-Node/browser platforms graphql-js has to work on though?)

It might be reasonable to just only do the "cancel stuff when execution is over" if global.AbortController exists. It's a bit more annoying to do this partial support if we add an AbortSignal to GraphQLRequestInfo since it would be nice if the TS type of the field is non-optional — but we could have that field be optional for now (with a "if you know you're in an AbortController-in-global environment then you can ! it") and non-optional once Node 14 support is dropped.

@@ -832,6 +867,14 @@ function completeValue(
result: unknown,
asyncPayloadRecord?: AsyncPayloadRecord,
): PromiseOrValue<unknown> {
if (exeContext.signal?.isAborted) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IvanGoncharov @yaacovCR any instincts on this — does it make more sense for "execution stopped due to AbortSignal" to return a partial result with field errors for all resolvers that run after abortion, or just a short error with no data?

@IvanGoncharov
Copy link
Member

@igrlk Sorry for the delay in the review.
I like the idea of reusing the abort functionality of a fetch.
Before merging, I want to do a quick check to ensure that API matches the W3C standards.

Also, I want to try removing delays from a test since we don't use them anywhere else, and the performance of the tests is critical for mutation testing.
If delays can't be easily removed, I'm ok to merge it as is, and I just want to try before merging.

That said, plan to check as much as possible and merge it tomorrow.

@igrlk
Copy link
Author

igrlk commented Jan 25, 2023

Thank you for checking this, @IvanGoncharov.

One thing I think is left besides what you mentioned - @glasser's question here regarding where the check for aborted execution should happen - in completeValue or no and how we should handle it. Do you have an opinion on that?

@IvanGoncharov
Copy link
Member

Update: I managed to remove setTimeout from tests but stuck on few other things including

@glasser's question #3791 (comment) regarding where the check for aborted execution should happen - in completeValue or no and how we should handle it. Do you have an opinion on that?@glasser's question #3791 (comment) regarding where the check for aborted execution should happen - in completeValue or no and how we should handle it. Do you have an opinion on that?@glasser's question #3791 (comment) regarding where the check for aborted execution should happen - in completeValue or no and how we should handle it. Do you have an opinion on that?

Need to think about it more, will continue tomorrow morning.

@IvanGoncharov
Copy link
Member

@igrlk Sorry for the long delay. It's ended up being a more complicated task than I initially thought.
I made some changes:

  1. Pass signal into resolvers
  2. move the place where we interrupt execution before we call resolver per @glasser suggestion
  3. Fix interruption of steam/defer
  4. Implement it on subscriptions (need to fix some edge cases)
    It failing some lints + need more testing, mainly steam/defer and subscription.
    But all existing tests are passing, including the new test added by this PR.
    Plan to finish it tomorrow but want to get early feedback on the changes.

Copy link
Author

@igrlk igrlk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for making this huge change @IvanGoncharov! I left some comments - please let me know what you think.

I'm also happy to follow up on this and finalise it (linting and other things left before merging). But I'll only have time by the end of the week.

@@ -261,6 +262,7 @@ export interface ExecutionArgs {
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
signal?: AbortSignal | undefined;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is AbortSignal defined globally in node@14? I think that I intentionally created an interface for it since it was not defined there, but I might be mistaken, need to double check

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, but TypeScript has typings for it.
Typescript knows what it is even on node@14, but it's only during type check in execution you can't call it on node@14 that's why I add typeof AbortController === 'undefined' check.

errors: [],
};
}

class ExecutionController {
/** For performance reason we can't use `signal.isAborted` so we cache it here */
isAborted: boolean = false;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For more context, what are the performance implications you mention here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a screenshot tomorrow.
But you can run npm run benchmark(locally) on top of my commit and see the difference.

isAborted: boolean = false;

private readonly _passedInAbortSignal: AbortSignal | undefined;
private readonly _abortController: AbortController | undefined =
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as about AbortSignal - I believe it was causing an error in node@14 so I had to add the interfaces that match AbortSignal and AbortController

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check on node@14. Maybe my assumption about TS detecting this type on node@14 is wrong.

return this._abortController?.signal;
}

abort(reason?: unknown) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can reason be typed as a string here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in normal use, it should be an Error object, but JS allows it to be a value at all, so we have no control over it 🤷‍♂️

@igrlk

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Feb 7, 2023

@github-actions publish-pr-on-npm

@igrlk The latest changes of this PR are available on NPM as
graphql@17.0.0-alpha.2.canary.pr.3791.e6d3ec58026d75b71b7b84c3da5f376ec7eeca94
Note: no gurantees provided so please use your own discretion.

Also you can depend on latest version built from this PR:
npm install --save graphql@canary-pr-3791

@igrlk
Copy link
Author

igrlk commented Feb 10, 2023

Screenshot 2023-02-10 at 18 12 44

AbortController and AbortSignal were not defined and failing the tests so I added the interfaces for them

IvanGoncharov added a commit to IvanGoncharov/graphql-js that referenced this pull request Feb 23, 2023
Motivation: technically node14 end of life will happen in two months so we can still support it but it creates problems with graphql#3791
Node14 doesn't support `AbortSignal` and since we want to allow resolvers to be canceled we need to expose this object through our public API (inside `info` argument).
Potentialy this and all the other issues (e.g. how to test this feature on node14) can be worked around but complicates solution.
So I think, it worth to deprecate node14 two months earlier in order to unblock graphql#3791 especially since we still in alpha stage.

Less critical but still significant problem with node14 is that it ships with npm6 which doesn't support default format of the lockfile
used by npm9 and this make updating dependencies trickier.
IvanGoncharov added a commit to IvanGoncharov/graphql-js that referenced this pull request Feb 25, 2023
Motivation: technically node14 end of life will happen in two months so we can still support it but it creates problems with graphql#3791
Node14 doesn't support `AbortSignal` and since we want to allow resolvers to be canceled we need to expose this object through our public API (inside `info` argument).
Potentialy this and all the other issues (e.g. how to test this feature on node14) can be worked around but complicates solution.
So I think, it worth to deprecate node14 two months earlier in order to unblock graphql#3791 especially since we still in alpha stage.

Less critical but still significant problem with node14 is that it ships with npm6 which doesn't support default format of the lockfile
used by npm9 and this make updating dependencies trickier.
@ladal1
Copy link

ladal1 commented Oct 2, 2023

Hey, we ran into issues with timeouts that started this whole chain and want to help get this over the finish line, I've tried to rebase @igrlk code onto newest main and it seems to pass completely fine (https://github.com/ladal1/graphql-js), do we need any more changes done (honestly got lost here in all the discussion)

@igrlk
Copy link
Author

igrlk commented Oct 2, 2023

Thanks, @ladal1
I've rebased the branch, fixed linters, but there are some failing tests. I don't have time to check it this week. Would you happen to have time to look into that?

@ladal1
Copy link

ladal1 commented Oct 2, 2023

Interesting, the tests were passing on my end when I copied it, thanks for the rebase, I'll take a look into those tests and try to make them pass ASAP

@igrlk
Copy link
Author

igrlk commented Oct 2, 2023

Thanks @ladal1 , I've invited you to be a collaborator in the fork

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Oct 2, 2023

CLA Signed

The committers listed above are authorized under a signed CLA.

@ladal1 ladal1 force-pushed the abort-execution branch 2 times, most recently from 77e6176 to 910a4ae Compare October 2, 2023 14:33
@ladal1

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Oct 2, 2023

@github-actions run-benchmark

@ladal1 Please, see benchmark results here: https://github.com/graphql/graphql-js/actions/runs/6381598227/job/17318384919#step:6:1

@ladal1
Copy link

ladal1 commented Oct 3, 2023

I'm fairly sure the behaviour is fixed, tests pass for me now, seems that either test edit of rebase mess caused the issues

@igrlk
Copy link
Author

igrlk commented Oct 3, 2023

We've intentionally added executionController.abort() statements in all those places before we return from executeImpl, though now I don't remember why exactly. Perhaps @IvanGoncharov knows better? Here is the code before rebasing and resolving conflicts:

try {
    const result = executeOperation(exeContext);
    if (isPromise(result)) {
      return result.then(
        (data) => {
          const initialResult = buildResponse(data, exeContext.errors);
          if (exeContext.subsequentPayloads.size > 0) {
            return {
              initialResult: {
                ...initialResult,
                hasNext: true,
              },
              subsequentResults: yieldSubsequentPayloads(exeContext),
            };
          }

          exeContext.executionController.abort();
          return initialResult;
        },
        (error) => {
          exeContext.executionController.abort();
          exeContext.errors.push(error);
          return buildResponse(null, exeContext.errors);
        },
      );
    }

    const initialResult = buildResponse(result, exeContext.errors);
    if (exeContext.subsequentPayloads.size > 0) {
      return {
        initialResult: {
          ...initialResult,
          hasNext: true,
        },
        subsequentResults: yieldSubsequentPayloads(exeContext),
      };
    }

    exeContext.executionController.abort();
    return initialResult;
  } catch (error) {
    exeContext.executionController.abort();
    exeContext.errors.push(error);
    return buildResponse(null, exeContext.errors);
  }

@igrlk
Copy link
Author

igrlk commented Oct 3, 2023

Re-comitting from my account to run pr checks, @ladal1

@ladal1
Copy link

ladal1 commented Oct 3, 2023

@igrlk I thought they weren't intentional as they didn't appear in the copy I had (but I might have had wrong revision), I'll try to take a look as to why they would be there and if it's possible to pass without removing them

@ladal1
Copy link

ladal1 commented Oct 3, 2023

Can't find why it should be there (and especially in all branches of the logic)

@igrlk

This comment has been minimized.

@github-actions
Copy link

@github-actions publish-pr-on-npm

@igrlk The latest changes of this PR are available on NPM as
graphql@17.0.0-alpha.3.canary.pr.3791.4a8f641106bee54f1e4a4de4bf59c49976541b00
Note: no gurantees provided so please use your own discretion.

Also you can depend on latest version built from this PR:
npm install --save graphql@canary-pr-3791

@igrlk
Copy link
Author

igrlk commented Oct 14, 2023

@ladal1 thanks for checking. I tried to reproduce cancellation in this repo, but if previously it was working as expected (at least in the initial versions of this PR), now it seems to hang and not finish the execution at all + the execution is not being aborted.

Will try to find some time to figure out why, but not sure when

JoviDeCroock added a commit that referenced this pull request Oct 23, 2024
This adds support for aborting execution from the outside or resolvers,
this adds a few tests and tries to make the support as easy as possible.

Do we want to support having abort support on subscriptions, I guess it
makes sense for server-sent events.

I've chosen 2 places to place these interrupts

- `executeFieldsSerially` - every time we start a new mutation we check
whether the runtime has interrupted
- `executeFields` - every time we start executing a new field we check
whether the runtime has interrupted
- inside of the catch block as well so we return a singular error, all
though this doesn't really matter as the consumer would not receive
anything
  - this here should also take care of deferred fields

When comparing this to `graphql-tools/execute` I am not sure whether we
want to match this behavior, this throws a DomException which would be a
whole new exception that gets thrown while normally during execution we
wrap everything with GraphQLErrors.

Supersedes #3791
Resolves #3764

Co-authored-by: yaacovCR <[email protected]>
@JoviDeCroock
Copy link
Member

Implemented in #4250

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.

7 participants