-
Notifications
You must be signed in to change notification settings - Fork 582
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
HttpRequest that survives past async functions #1036
Comments
Internally it should be uWS::HttpRequest* = uWS::HttpRequest*->makeDynamicClone(); it just makes 1 block with the headers and all |
Those. if we call |
A wrapper is: function myFetch(request) { app.get('*', (res, req) => { |
Yes if you call keep() it would move from being a stack allocated thing to being dynamic and available until it gets GC:d. So it's a very simple function and wrappers would not need to copy out all headers one by done but can just keep the Request after calling keep() on it |
But it's slow as heck, so the key here is to only do it if the first invocation of the CB does not return a Response or calls end. So the whole idea is: only use it for slow path |
I'm understood, thank you. But the problem with all abstractions is that they usually do not know whether the request handler will be executed in the fast way or in the slow way, so the abstraction always chooses the slow way to be universal, so they will always call the keep method. By what percentage does the response slow down if you always call the request method keep? |
You don't need to know if it will be. You need to know if it was. Finding out if the request was ended is easy. That's the whole point of doing this after as a late stage slow path |
The library could even do this automatically, but it would add a cost to all async handlers. But those are kind of costly either way |
Making it automatic looks easiest. Then it would just work. And wrappers can remove all their header copying crap and always use the given Request object |
Here's a more realistic example of abstraction: async function myFetch(request) {
return await sql;
}
app.get('*', async (response, request) => {
try {
const result = await myFetch(request);
response.end(result);
} catch (error) {
response.end(error);
}
}); Comment from your example:
There are misconceptions in this statement, the fact is that it is impossible to find out the state of a promise, whether it has resolved or not, such a promise interface, we can only wait for the result, so this is always the slow way and always calls the keep method |
There is no misconception. The library knows perfectly well whether the Response has ended or not. Like said, async/await functions will always take the slow path for a number of reasons (in Node.js, the microtask queue is not drained immediately after a call into JS which is terrible for perf. but I don't care it is a Node.js problem and doesn't affect non-async functions) |
Non-async functions like those in HelloWorld.js will end the request by calling Response.end() immediately and in this case there is no overhead since it never clones the request object |
No, the point of abstractions is that the application code of the route handlers does not know anything about the implementation of the server, i.e. an asynchronous function that an abstraction calls never calls a response end, because this is the exclusive privilege of the abstraction, those. The response state after calling the handler will always be unfinished. P.S. |
"the library" = uWS. It knows whether you responded or not |
Then the uWS should automatically call keep method for all asynchronous handlers, i.e. all asynchronous requests will be on the slow path, this will reduce the speed? |
I think what you mean @uasan is that you'd like an option to "not" copy the request if you know you will not use anything from it in some of your async handlers? |
Yes, it’s better that by default uWS doesn’t do something that can be expensive, I agree if copying is done only explicitly, through calling the |
it could be negligible in comparison with async flow either way. needs benchmarking |
This is a great proposal and would significantly help performance for wrappers / libraries like As for the best way of implementing it, automatic is ideal but having a method like |
I am asssuming to automate this behavior, you will be checking to see whether a response was sent after the route handler returns? I use a similar trick here to determine whether I need to cork before sending a response with a boolean flag: One more thing that I thought of is wouldn't someone calling |
There are ways to easily support more abstract interfaces such as the fetch one, without any significant perf. loss in fast path.
Request is stack allocated and is only valid for the "sync" callback. Adding a call, Request.keep() that would dynamically allocate it and make it "keepable" could be automatically done for callbacks that do not return a Response.
This means, in the fast path, there is no overhead, while in the slow async path, you can still easily "keep" the Request for as long as you need as a slow path.
The benefit here is obvious: wrappers that implement more abstract interfaces do not need to:
This is really bad for fast path performance
Internally, all that is neeed is adding 2 functions: getMemory, setMemory so that keep() calls malloc, getMemory, memcpy, setMemory on the Request and drop calls free
The text was updated successfully, but these errors were encountered: