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

response is only finished if socket is detached #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ronag
Copy link

@ronag ronag commented Jun 24, 2019

Refs: #30

Copy link
Contributor

@dougwilson dougwilson left a comment

Choose a reason for hiding this comment

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

Thanks!

If these are two different fixes, please split into two PRs. If not, then no problem. There is no description or tests so I'm not sure (a) if they are the just one fix or two separate fixes (b) what specifically they are fixing and (c) if they consistently fix the issue across the supported Node.js versions this module supports.

@ronag ronag changed the title Minor fixes finished is only finished if socket is detached Jun 24, 2019
@ronag ronag changed the title finished is only finished if socket is detached response is only finished if socket is detached Jun 24, 2019
@ronag
Copy link
Author

ronag commented Jun 24, 2019

Sorry, fixed!

@ronag
Copy link
Author

ronag commented Jun 24, 2019

This PR does make the situation better. But it does not fully solve the issue.

If end() is called before socket is emitted it will still give a false positive. I don't know how to fix that at the moment...

@ronag ronag force-pushed the master branch 5 times, most recently from c5a5131 to 7c8d7f3 Compare June 24, 2019 23:55
@ronag
Copy link
Author

ronag commented Jun 24, 2019

Ok, I think I've found a way that catches all edge cases and also doesn't break http/2 compat.

@ronag
Copy link
Author

ronag commented Jun 24, 2019

(!socket && msg.finished && msg.outputSize === 0)

!socket is true if msg has not connected or has closed
msg.finished checks that end() has been called
msg.outputSize === 0 checks that there is no pending data

The logic here is a bit complicated.

We can't just rely on !socket since it will be a false positive if still not connected yet. However, if finished is true that means that some data must have been queued on the msg object which means that outputSize > 0 unless a socket has been assigned (even if it no longer is assigned).

@ronag ronag force-pushed the master branch 2 times, most recently from a20c73d to e73e839 Compare June 25, 2019 00:05
index.js Outdated
if (msg.stream) {
// Http2ServerRequest
// Http2ServerResponse
return msg.stream.closed
Copy link
Author

@ronag ronag Jun 25, 2019

Choose a reason for hiding this comment

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

Note this is needed in order to not break http2 compat which does not implement outputSize.

@ronag ronag force-pushed the master branch 2 times, most recently from ec12f71 to 200fb77 Compare June 25, 2019 07:15
@ronag
Copy link
Author

ronag commented Jun 25, 2019

Updated test

@ronag
Copy link
Author

ronag commented Jun 25, 2019

Fails in node < 0.4. Is that a problem?

@ronag
Copy link
Author

ronag commented Jul 2, 2019

Is there anything more I can do here?

@dougwilson
Copy link
Contributor

Hi @ronag . Sorry I have been out of a computer at the moment. This is a core piece of Express, and was created in support of the Express project. It looks like there are two core issues that block merging this currently: there is uncovered code in the PR (the coveralls check failure) and the failed CI on supported platforms (< 4, as you noted above).

I may be able to help you get those two things fixed up, though, if you're not sure how. If you're stuck on getting the checks on the PR resolved, please let me know and I can lend a hand.

@ronag
Copy link
Author

ronag commented Jul 2, 2019

The coveralls and node < 4 are a bit out of my expertise.

@dougwilson
Copy link
Contributor

It's no problem at all, I can help out. So for the coveralls, it would just indicate that it is unreached code, as in there is no tests added to cover the added branches/lines.

I can help construct tests and add the < 4 support if you can help me understand the changes in this PR and/or how you've been manually testing the changes, which I can help replicate into automated tests.

@ronag
Copy link
Author

ronag commented Jul 2, 2019

you can help me understand the changes in this PR and/or how you've been manually testing the changes, which I can help replicate into automated tests.

So the problem this is trying to fix is the fact that msg.finished only tells whether end() has been called or not, and not whether 'error', 'aborted' or 'end' has been emitted (i.e. the response has finished).

There is currently no easy way to query this information from the response object in the success scenario ((socket && !socket.writable) handles 'error'). But here is what I was able to figure out for the success case.

(!socket && msg.finished && msg.outputSize === 0)

  • !socket is true if msg has not connected or has closed
  • msg.finished checks that end() has been called
  • msg.outputSize === 0 checks that there is no pending data

The logic here is a bit complicated.

We can't just rely on !socket since it will be a false positive if still not connected yet. However, if finished is true that means that some data must have been queued on the msg object which means that outputSize > 0 unless a socket has been assigned (even if it no longer is assigned).

I've updated the test to check that it is not finished after end.

Furthermore, I had to add some extra code (hence the coveralls failure) to ensure we don't break node http/2 compat which does not have a outputSize but does have a property to check what we are looking for, i.e. stream.closed.

I suspect node < 4 does not have outputSize which is why it fails.

@dougwilson
Copy link
Contributor

Gotcha. So this module (in the 1.x form) does consider the response finished after res.end is called, even if the data has not been flushed completely yet. So it sounds like this is a proposal for a 2.0 anyway, where the definition of a "finished" response is changed, is that accurate?

@ronag
Copy link
Author

ronag commented Jul 2, 2019

Another way to phrase it. This PR fixes so that ‘isFinished’ is never true before ‘onFinished’ has been called.

@dougwilson
Copy link
Contributor

Well, more like this module's design was just to add a callback to the definition of what node.js http server considered finished, i.e. the res.finished property. And it sounds like you're saying this module is, indeed, following with what node.js defines res.finished as, but your idea of what the word "finished" means in the definition of a http response differs. I get it.

And I'm not arguing otherwise, only noting that this is not a bug fix, because the module is operating exactly as intended. It can certainly be made to change this definition away from the node.js finished property as a new major iteration of this module if that is what is desired here.

But in the same vein, it actually sounds like the bug is that the callback this module tried to add on top of the res.finished is being called too late, that res.finished turns true quite some time before the callback is emitted? That would be the bug fix, in my eyes, where as this PR is a definition change of "finished".

Remember: this module was built around adding a callback to the res.finished property as it's core purpose. It's in the name of the module, even :)

@dougwilson
Copy link
Contributor

Anyway, regardless, I still want to help land this pull request, so maybe we should focus on the tasks at hand for that 👍

So I had originally saw your multiple commit iterations, and there is the http/2 issue. Were you testing the changes by hand? If so, can you help me replicate that setup? I can do the leg work to then translate into automated test cases in the test suite.

@ronag
Copy link
Author

ronag commented Jul 12, 2019

So I had originally saw your multiple commit iterations, and there is the http/2 issue. Were you testing the changes by hand? If so, can you help me replicate that setup? I can do the leg work to then translate into automated test cases in the test suite.

I was just reviewing the code. I don't have a setup for h2.

@ronag
Copy link
Author

ronag commented Jul 12, 2019

This is also relevant nodejs/node#28621

@ronag
Copy link
Author

ronag commented Jul 14, 2019

nodejs/node#28681

return (
msg.finished &&
msg.outputSize === 0 &&
(!socket || socket.writableLength === 0)
Copy link
Author

@ronag ronag Aug 17, 2019

Choose a reason for hiding this comment

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

This has landed in node 12 as writableFinished.

Choose a reason for hiding this comment

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

Typo: writtableFinished writableFinished.

@wesleytodd
Copy link
Member

Hey @ronag! I am trying to do some house cleaning and just stumbled on this and #32. Can you help me come up to speed on this and decide on next steps?

And I'm not arguing otherwise, only noting that this is not a bug fix, because the module is operating exactly as intended. It can certainly be made to change this definition away from the node.js finished property as a new major iteration of this module if that is what is desired here.

At first I saw both of these as bug fixes, but this makes me question it. We can certainly major version rev this if necessary (and maybe should just to drop old node versions), but I think the main thing we want to ensure is that this behavior aligns with the tag line "Execute a callback when a HTTP request closes, finishes, or errors".

I don't want to nit pick the original purpose of this package, only to resolve this so that it best serves the users of this (directly or via express), so if that is landing this I am for it.

@IamLizu
Copy link
Member

IamLizu commented Aug 20, 2024

A quick question, the problem mentioned in #30, is this a correct reproduction?

const express = require('express');
const onFinished = require('on-finished');

const app = express();

app.get('/', (req, res) => {
    res.write('Initial data...\n');

    // End the response
    res.end('Response ended.\n');

    // Simulate an error after the response is ended
    setTimeout(() => {
        res.emit('error', new Error('An error occurred after .end()'));
    }, 1000);

    onFinished(res, (err) => {
        if (err) {
            console.log('Error occurred after response ended:', err.message);
        } else {
            console.log('Response finished.');
        }
    });
});

app.listen(3000, () => {
    console.log('App running on port 3000');
});

cc: @ronag @wesleytodd

@wesleytodd
Copy link
Member

This is all from quite a long time ago. I think it is a reproduction, but without digging in again I don't want to say with certainty. I would look to you to prove this out if you would like to help land this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants