Skip to content

Fix 677#692

Merged
delvedor merged 11 commits intomasterfrom
fix-677
Jan 22, 2018
Merged

Fix 677#692
delvedor merged 11 commits intomasterfrom
fix-677

Conversation

@delvedor
Copy link
Copy Markdown
Member

This pr fixes #677.
In addition will release the reusify holder which is running the hooks, in this way the memory usage will not increase during time.

Related: delvedor/fast-iterator#8 and fastify/middie#17.

Checklist

  • run npm run test and npm run benchmark
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message and code follows Code of conduct

@delvedor delvedor added enhancement bugfix Issue or PR that should land as semver patch labels Jan 20, 2018
Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

LGTM

@delvedor
Copy link
Copy Markdown
Member Author

I'll wait for fastify/middie#17 before merge this.

Comment thread fastify.js

function hookIterator (fn, state, next) {
function hookIterator (fn, state, next, release) {
if (state.res.finished === true) return release()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If the hook sends a stream, will res.finished be true here? You may need to use res.headersSent instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll test this tomorrow :)

@nwoltman
Copy link
Copy Markdown
Contributor

There should probably be some documentation to tell people using the callback version of hooks/middleware that if they send a response early, they must still call next() or else there will be a memory leak.

Comment thread test/hooks-async.js
})
})

test('onRequest hooks should be able to block a request', t => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe these cases should be worded: "should be able to terminate a request"? The word "block" seems to imply something more sinister to me.

Copy link
Copy Markdown
Member

@jsumners jsumners left a comment

Choose a reason for hiding this comment

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

The associated issue speaks specifically about using async handlers. Can we get a test that shows how those should work?

@delvedor
Copy link
Copy Markdown
Member Author

delvedor commented Jan 20, 2018

@jsumners I've already written them, check again :P

@nwoltman 👍

@jsumners
Copy link
Copy Markdown
Member

@delvedor oops, I see.

Copy link
Copy Markdown
Member

@jsumners jsumners left a comment

Choose a reason for hiding this comment

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

LGTM

@delvedor
Copy link
Copy Markdown
Member Author

I've added a test case for streams. The issue is that res.finished or res.headersSent or res.connection._writableState.writing are always false, even if we call next inside a setImmediate or inside a nextTick.
At the moment the solution is to use res.once('finish', next), in this way you will not encounter errors. Everything has been documented.

Comment thread docs/Hooks.md Outdated

// async api
fastify.addHook('preHandler', async (request, reply) => {
reply.code(401).send(new Error('Unauthorized'))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A better example would be to send something that isn't an Error because you can already do this with next(new Error('Unauthorized') for callbacks and throw new Error('Unauthorized') for async.

@nwoltman
Copy link
Copy Markdown
Contributor

@delvedor Thanks for figuring out how to make this work with streams :)

There's 1 case missing though. If the very last hook/middleware sends a response, then you also need to check res.finished in the callback for the hooks (because hookIterator isn't called after the last hook).

@delvedor
Copy link
Copy Markdown
Member Author

@nwoltman nice catch! :)

@delvedor
Copy link
Copy Markdown
Member Author

LOL, 42 additions of docs, 12 of core library and 555 of tests :D

Copy link
Copy Markdown
Contributor

@nwoltman nwoltman left a comment

Choose a reason for hiding this comment

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

Almost there! Just one last bit for the middlewares.

Comment thread fastify.js
}

function middlewareCallback (err, state) {
if (state.res.finished === true) return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same thing in onRunMiddlewares

Copy link
Copy Markdown
Member Author

@delvedor delvedor Jan 21, 2018

Choose a reason for hiding this comment

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

There should be no need for that test. The middlewares are handled by middie, and the res.finished check is performed before to call the iterator or complete. :)
https://github.com/fastify/middie/blob/master/middie.js#L70-L77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh that's included in middie. Cool!

Copy link
Copy Markdown
Contributor

@nwoltman nwoltman left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

The code is ok. I would not document the stream parts of preHandler as it should not be used that way, especially with async/await.

Comment thread docs/Hooks.md Outdated
Note: If you change the payload, you may only change it to a `string`, a `Buffer`, a `stream`, or `null`.

### Respond to a request from an hook
If need you can respond to a request before you reach the route handler, an example could be an authentication hook. If you are using `onRequest` or a middleware just use `res.end`, if you are using the `preHandler` hook use `reply.send`. Remember to always call `next` if you are using the standard hook api, if you are working with *async* hooks it will be done automatically by Fastify.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not true. If you 'forget' to call next, the only harm you will cause is to potentially increase the memory consumption, and you will prevent one of our optimizations to kick in.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@delvedor @mcollina Sorry for suggesting this. I misunderstood how reusify works. I now see that if you don't call holder.release(obj) the object just won't be reused and it will be garbage-collected (so it doesn't cause a memory leak).

Comment thread docs/Hooks.md Outdated
const stream = fs.createReadStream('some-file', 'utf8')
stream.pipe(res)
res.once('finish', next)
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Be careful that this code is substantially wrong because it will leak a file descriptor if stream errors. How about using pump?

Comment thread docs/Hooks.md Outdated
fastify.addHook('preHandler', (request, reply, next) => {
const stream = fs.createReadStream('some-file', 'utf8')
reply.send(stream)
reply.res.once('finish', next)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should really not be needing this.
We should not really be encouraging anyone to do this, so it's better if we do not document this behavior.
Doing this in their code is substantially wrong.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So your suggestion is to remove the documentation about respond with streams inside the hooks?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would say so. We definitely need a better story for this hook, and there is solid work already done here which I do not want to block. Can you open a separate issue about replying with streams in a 'preHandler'?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok, I've updated the docs.

Comment thread docs/Hooks.md Outdated
const stream = fs.createReadStream('some-file', 'utf8')
stream.pipe(res)
res.once('finish', resolve)
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is even worse.

@delvedor delvedor merged commit d89079c into master Jan 22, 2018
@delvedor delvedor deleted the fix-677 branch January 22, 2018 20:44
@github-actions
Copy link
Copy Markdown

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Feb 20, 2022
@Eomm Eomm added the semver-minor Issue or PR that should land as semver minor label Apr 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

bugfix Issue or PR that should land as semver patch semver-minor Issue or PR that should land as semver minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

End request in async preHandler

5 participants