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

fix server listen error event many times #66

Closed
wants to merge 10 commits into from

Conversation

pagewang0
Copy link

No description provided.

@Gerhut
Copy link
Owner

Gerhut commented May 28, 2024

I reviewed the codebase and found that the unit test did not cover the server error but only the socket error, and the server error was not handled correctly. Resolved in de20de8

Could you rebase your PR and see if the test cases are still working correctly?

@pagewang0
Copy link
Author

I reviewed the codebase and found that the unit test did not cover the server error but only the socket error, and the server error was not handled correctly. Resolved in de20de8

Could you rebase your PR and see if the test cases are still working correctly?

Yes, the test cases are working with no problems!

@coveralls
Copy link

coveralls commented May 28, 2024

Coverage Status

coverage: 100.0%. remained the same
when pulling 54969d2 on pagewang0:master
into 511eef4 on Gerhut:master.

index.js Outdated
@@ -51,7 +51,9 @@ const createAdapter = handler => config => new Promise((resolve, reject) => {
const server = handler instanceof Server ? handler : createServer(handler)
const listening = server.listening

server.on('error', reject);
if (server.eventNames().includes('error')) {
Copy link
Owner

Choose a reason for hiding this comment

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

Does it setup error handler only if there is already one?

Copy link
Author

Choose a reason for hiding this comment

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

No, because the server listens for errors using on, not once. You can try emitting the error many times.

@@ -71,7 +73,6 @@ const createAdapter = handler => config => new Promise((resolve, reject) => {
}
).then(
(response) => {
server.off('error', reject)
Copy link
Owner

@Gerhut Gerhut May 28, 2024

Choose a reason for hiding this comment

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

Why does it remove?

Copy link
Author

Choose a reason for hiding this comment

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

Because this code can fix the server's repeated listening problem.

if (server.eventNames().includes('error')) {
    server.on('error', reject)
  }

Copy link
Owner

Choose a reason for hiding this comment

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

Can you add a teat case to describe your issue?

Copy link
Author

Choose a reason for hiding this comment

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

That is my error, I mean code is this

if (!server.eventNames().includes('error')) {
    server.on('error', reject)
}

I will write test case to describe my issue.

Copy link
Author

Choose a reason for hiding this comment

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

const server = createServer((req, res) => res.end())

await axiosist(server).get('/')
t.is(server.listenerCount('error'), 0)
t.is(server.listenerCount('error'), 1)
Copy link
Owner

@Gerhut Gerhut May 28, 2024

Choose a reason for hiding this comment

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

No, the error listener is not supposed to be kept here, which is causing memory leak.

const server = createServer((req, res) => res.end('foo'))

await t.throwsAsync(axiosist(server).get('/', { maxContentLength: 1 }))
t.is(server.listenerCount('error'), 0)
t.is(server.listenerCount('error'), 1)
Copy link
Owner

Choose a reason for hiding this comment

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

Neither

await t.throwsAsync(axiosist(server).get('/'), { message: 'foo' })

server.on('error', (err) => {
t.is(err.message, 'foo')
Copy link
Owner

Choose a reason for hiding this comment

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

This is not testing axiosist but testing node.js's http module


await Promise.all(promises.map(promise => promise()))

t.is(server.listenerCount('error'), 1)
Copy link
Owner

Choose a reason for hiding this comment

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

This doesn't make sense - only the first request will listen to the server error, others will hang if server emits error event.

Copy link
Author

Choose a reason for hiding this comment

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

I tried running your off error code. Can you run this test case?:

test('should be listened error to once', async t => {
  const server = createServer()

  server.on('request', (req, res) => {
    res.end('ok')
  })

  server.listen()

  const request = axiosist(server)
  const promises = []

  for (let i = 0; i < 20; i++) {
    promises.push(() => request.get('/'))
  }

  await Promise.all(promises.map(promise => promise()))

  t.is(server.listenerCount('error'), 0)
})

The repeated listening error still exists. If an error is emitted, then many listeners will receive the error message because your off operation is asynchronous.

Copy link
Owner

Choose a reason for hiding this comment

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

Consider:

test('should be listened error to once', async t => {
  const request = axiosist((req, res) => {
    res.end('ok')
  })
  const promises = []

  for (let i = 0; i < 20; i++) {
    promises.push(() => request.get('/'))
  }

  await Promise.all(promises.map(promise => promise()))
})

This does not seem a typical unit test, if you need concurrent requests to the same server with such number, consider stress tests like autocannon.

Copy link
Author

Choose a reason for hiding this comment

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

Maybe you are right. I just want to prove the error listening issue.

Copy link
Owner

Choose a reason for hiding this comment

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

I am thinking about maybe we can just drop this event listener - serious server errors will be transferred to the socket, which is already handled by axios (one example: https://github.com/axios/axios/blob/v1.7.2/lib/adapters/http.js#L571-L574).

Copy link
Author

Choose a reason for hiding this comment

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

Usually, when the server encounters an error, it is handled by a try-catch block or its own listener, so I think this error listener can be removed too.

@pagewang0 pagewang0 closed this May 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants