-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Coroutinize http client code #2429
base: master
Are you sure you want to change the base?
Conversation
And brush up the if-else nesting a little bit to avoid co_return-s Signed-off-by: Pavel Emelyanov <[email protected]>
For simplicity, wrap the entrance `if` around. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
And leave indentation broken until next patch Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
The former helper was introduced to reduce the netsing of .then-chains. Now when it's a coroutinie, it's better be squashed with its only caller. Signed-off-by: Pavel Emelyanov <[email protected]>
Same as previous patch -- the former method was a way to reduce indentation level for the latter. Now they look good enough when squashed. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
There's a trickery there. After _closed future is resolved "this" may become freed already. So to make the last log message printed, the this->local_address was saved into .then capture. When coroutinized, that field is kept on stack. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
There's a trickery here. After con->close() is resolved, the con may be the last pointer holding the connection alive. Before this patch, it was saved on .finally capture, after it -- it's preserved as on-stack variable. Signed-off-by: Pavel Emelyanov <[email protected]>
In its .then-chain form the method calls itself to emulate "goto again" without goto. To do the same with coroutines, wrap it into a loop. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
This method closes all its connection by calling itself until the pool is empty. With coroutines it is done with the plain loop. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
1618829
to
21a5b8c
Compare
@@ -191,7 +191,7 @@ private: | |||
future<> shrink_connections(); | |||
|
|||
template <std::invocable<connection&> Fn> | |||
auto with_connection(Fn&& fn, abort_source*); | |||
futurize_t<std::invoke_result_t<Fn, connection&>> with_connection(Fn fn, abort_source*); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, i believe Avi would like this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has less than 4 closing corner braces in a row, so not that bad
Meanwhile in scylla (scroll right):
test/boost/user_function_test.cc:769: e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS map<int, frozen<set<frozen<list<frozen<tuple<text, bigint>>>>>>> LANGUAGE Lua AS \
😱
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, i meant, he always prefers an explicit return type. this one make it more explicit. so..
@@ -341,13 +341,11 @@ future<> client::do_make_request(connection& con, request& req, reply_handler& h | |||
connection::reply_ptr reply = co_await con.do_make_request(req); | |||
auto& rep = *reply; | |||
if (expected.has_value() && rep._status != expected.value()) { | |||
if (!http_log.is_enabled(log_level::debug)) { | |||
throw httpd::unexpected_status_error(reply->_status); | |||
if (http_log.is_enabled(log_level::debug)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd prefer early return for less indent. but don't feel strong either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to de-duplicate two throw-s of the same exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left some comments.
Signed-off-by: Pavel Emelyanov <[email protected]>
When reply contains unexpected status, client may print its body in logs. Before being coroutinized it was two disctinct chains, with coroutines it makes sense to rewrap them a little bit. Signed-off-by: Pavel Emelyanov <[email protected]>
It used to call itself to "goto again". With coroutines it won't work as nice, neither it looks good enough with the outer loop :( So use goto explicitly. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
It makes some non-trivial decisions on whether or to retry the request in the catch block, so the conversion is a bit hairy. Signed-off-by: Pavel Emelyanov <[email protected]>
Signed-off-by: Pavel Emelyanov <[email protected]>
21a5b8c
to
67134fd
Compare
upd:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
As a general note when I tried to co-routinize cql server in Scylla I did not find a way to do so without introducing more allocations when requests are pipelined. With continuations if we read more than one request from a socket all of them can be processed without allocating continuations since futures will be ready. With co-routins we always allocate. |
With [[clang::coro_elide_safe]] nested coroutines can share the coroutine frame. However it only arrives in clang 20 and isn't standardized. |
return _write_buf.write(req.content); | ||
} else { | ||
return make_ready_future<>(); | ||
co_await _write_buf.write(req.content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this add an allocation in the common case that the buffer isn't full?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but all users we have use lambda body writer and don't step on this branch. But lambda body writer also adds allocation here
src/http/client.cc
Outdated
if (req.get_header("Expect") == "") { | ||
return make_ready_future<reply_ptr>(nullptr); | ||
if (req.get_header("Expect") != "") { | ||
co_await _write_buf.flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The common case is, that Expect will be empty, no? so we add an allocation here.
Please measure the performance of the series.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The common case is, that Expect will be empty, no? so we add an allocation here.
The allocation is unavoidable here anyway, at the end this method does co_await recv_reply()
and it will wait for it
Please measure the performance of the series.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please measure the performance of the series.
With #2442 , the test makes 1M requests in
- master: 3.40 +/- 0,02 seconds (min 3,37 / max 3,50)
- this PR: 3,48 +/- 0,02 seconds (min 3,45 / max 3,62)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please report allocs/op
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- master: 45 allocs/req
- this PR: 46 allocs/req
Hi @xemul, When you created a 28-patch series which must have taken you many many hours to create, please spend another 10 minutes trying to explain your motivation for doing this. Was it just the generic "coroutines are nicer" sentiment that we all shared, or something specific which bothered you? Also, I see a lot of discussion regarding the lower performance of the coroutine code, so I wonder if you made an attempt to benchmark the http server somehow to see if there's anything we need to worry about. |
No, nothing specific. It just looks much nicer and simpler this way.
I made a silly test with existing http client demo, but it's not very representative. I'll update the discussion thread with better measurement |
The code looks nicer and with much less indentation depth that way