Skip to content

Conversation

@delvedor
Copy link
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
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
Member Author

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


function hookIterator (fn, state, next) {
function hookIterator (fn, state, next, release) {
if (state.res.finished === true) return release()
Copy link
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
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
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.

})
})

test('onRequest hooks should be able to block a request', t => {
Copy link
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
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
Member Author

delvedor commented Jan 20, 2018

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

@nwoltman 👍

@jsumners
Copy link
Member

@delvedor oops, I see.

Copy link
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
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.

docs/Hooks.md Outdated

// async api
fastify.addHook('preHandler', async (request, reply) => {
reply.code(401).send(new Error('Unauthorized'))
Copy link
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
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
Member Author

@nwoltman nice catch! :)

@delvedor
Copy link
Member Author

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

Copy link
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.

}

function middlewareCallback (err, state) {
if (state.res.finished === true) return
Copy link
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
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
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
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
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.

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
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
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).

docs/Hooks.md Outdated
const stream = fs.createReadStream('some-file', 'utf8')
stream.pipe(res)
res.once('finish', next)
})
Copy link
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?

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
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
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
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
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.

docs/Hooks.md Outdated
const stream = fs.createReadStream('some-file', 'utf8')
stream.pipe(res)
res.once('finish', resolve)
})
Copy link
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

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

6 participants