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

apollo-server's stop() hangs when running in jest jsdom browser-emulation mode due to lack of timer.unref #4933

Closed
nogashimoni opened this issue Feb 16, 2021 · 5 comments · Fixed by #5498

Comments

@nogashimoni
Copy link

nogashimoni commented Feb 16, 2021

  • I write tests uning Jest + react testing library + jsdom 15. In one of the tests I use ApolloServer - I start it beforeAll and stop it after all.
    When I used apollo-server version 2.19.0 everything worked as expected. When upgrading to apollo-server version 2.21.0,
    server.stop() hangs forever. I try using stopGracePeriodMillis and that doesn't work as well.

  • [ apollo-server version 2.21.0 ] The package name and version of Apollo showing the problem.

  • [ apollo-server version 2.19.0 ] The last version of Apollo where the problem did not occur.

  • [ server.stop() should work and stop the server ] The expected behavior.

  • [ instead It's stuck and never reaches the following code ] The actual behavior.

  • A simple, runnable reproduction!

import React from 'react';
import { gql } from '@apollo/client';
import { ApolloServer } from 'apollo-server';

// Just a random schema
const typeDefs = gql`  
  type Query {
    books: [Book]!
  }
  type Book {
    title: String
    author: Author
  }
  type Author {
    name: String
    books: [Book]
  }
`;

let mockServer;

beforeAll(async () => {
  mockServer = new ApolloServer({typeDefs});
  try {
    const { url } = await mockServer.listen({
      port: 0
    });
    console.log(`🚀 Server ready at ${url}`);
    return url;
  } catch (error) {
    console.error('Error running server', error);
    throw error;
  }
});

afterAll(async () => {
  await mockServer?.stop();
  console.log('stopped server'); // This line is never reached
});

describe('VesselName', () => {
  it('Does nothing', async () => {
  });
});

My guess is that this change broke the stop functionality, maybe because the code runs from jsdom browser + jest, and stoppable might have issues with it.

Update:
I think my guess is correct, I found this in the jsdom browser console:

  console.error node_modules/jest-jasmine2/build/jasmine/Env.js:290
    TypeError: setTimeout(...).unref is not a function
        at Immediate.<anonymous> (/Users/nogashimoni/Documents/projects/javascript/marint-ui/node_modules/stoppable/lib/stoppable.js:43:39)
        at processImmediate (internal/timers.js:461:21)

Thank you!

@nogashimoni nogashimoni changed the title server.stop hangs when running from jest server.stop() hangs when running from jest Feb 16, 2021
@nogashimoni
Copy link
Author

When I run the code not from jest, everything works fine and the server stops, so I assume it's related to the fact that I run this from Jest + react testing library, and that the code runs from jsdom .

(This works perfectly fine:

const React = require('react')
const { gql }  = require( '@apollo/client')
const { ApolloServer } = require('apollo-server');

// Just a random schema
const typeDefs = gql`  
  type Query {
    books: [Book]!
  }
  type Book {
    title: String
    author: Author
  }
  type Author {
    name: String
    books: [Book]
  }
`;

async function demo() {
  mockServer = new ApolloServer({typeDefs});
  try {
    const { url } = await mockServer.listen({
      port: 0
    });
    console.log(`🚀 Server ready at ${url}`);
  } catch (error) {
    console.error('Error running server', error);
    throw error;
  }
  await mockServer?.stop();
  console.log('stopped server'); // This line is reached when not running from Jest
}

demo();

)

@nogashimoni
Copy link
Author

nogashimoni commented Feb 17, 2021

Update - it seems that the stoppable code can't run from jsdom.

As a temp solution, I added a polyfill for setTimeout(...).unref, like so:

  const setTimeoutProto = window.setTimeout().__proto__;
  if (!setTimeoutProto.unref) {
    setTimeoutProto.unref = function () {};
  }

It's not a good solution IMO, but it makes server.stop() work and that's good enough for me for now.

@glasser
Copy link
Member

glasser commented Feb 23, 2021

Hi, I appreciate the test file but since it sounds like this issue is very specific to the particular way you run jest with jsdom, is it possible to give a bit more context on how to run it? eg maybe as a git repo I can clone, or something like codesandbox.io?

If the issue is unref, then one workaround would be to pass stopGracePeriodMillis: Infinity to new ApolloServer. This means that the new stoppable functionality will still kill idle HTTP connections but just won't kill ones that are actively running an HTTP request. For a test, this actually seems like a reasonable approach since your tests probably won't end with active HTTP requests (and if they do, Jest's timeouts will kick in?).

I'm definitely also open to forking stoppable (either into @apollographql/stoppable or just copying the single file into this repo). I think we could replace the use of unref by calling clearTimeout from the server.close callback.

@glasser
Copy link
Member

glasser commented Feb 23, 2021

Let's see if upstream takes hunterloftis/stoppable#33
In the meantime, let me know if stopGracePeriodMillis: Infinity is a reasonable workaround.

@glasser glasser changed the title server.stop() hangs when running from jest apollo-server's stop() hangs when running in jest jsdom browser-emulation mode due to lack of timer.unref Feb 23, 2021
glasser added a commit that referenced this issue Jul 19, 2021
glasser added a commit that referenced this issue Jul 23, 2021
@glasser
Copy link
Member

glasser commented Aug 2, 2021

(In AS3.1, we inlined the stoppable package and made a bunch of improvements including fixing this issue.)

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants