Skip to content

Commit 1b94803

Browse files
yagopclaude
authored andcommitted
Apply backpressure to inbound HTTP connections
HttpConnectionBase::loop() used to drain the socket unconditionally, so when data arrived faster than the query parser consumed it (e.g. a big file upload saved to a slow disk), unbounded amounts of received data accumulated in memory: uploading a 512 MB file could transiently hold more than 1 GB in buffers. Stop reading from the socket while more than MAX_PENDING_READ_SIZE of received data is pending in memory. The kernel receive buffer then fills up and TCP flow control throttles the peer, keeping memory usage constant however large the request is. The same upload now peaks below 8 MB of buffer memory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d8d46df commit 1b94803

2 files changed

Lines changed: 21 additions & 6 deletions

File tree

tdnet/td/net/HttpConnectionBase.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,16 @@ void HttpConnectionBase::loop() {
100100
sync_with_poll(fd_);
101101
if (can_read_local(fd_)) {
102102
LOG(DEBUG) << "Can read from the connection";
103-
auto r = fd_.flush_read();
104-
if (r.is_error()) {
105-
if (!begins_with(r.error().message(), "SSL error {336134278")) { // if error is not yet outputted
106-
LOG(INFO) << "Receive flush_read error: " << r.error();
103+
auto pending_read_size = read_sink_.get_read_size();
104+
if (pending_read_size < MAX_PENDING_READ_SIZE) {
105+
auto r = fd_.flush_read(MAX_PENDING_READ_SIZE - pending_read_size);
106+
if (r.is_error()) {
107+
if (!begins_with(r.error().message(), "SSL error {336134278")) { // if error is not yet outputted
108+
LOG(INFO) << "Receive flush_read error: " << r.error();
109+
}
110+
on_error(Status::Error(r.error().public_message()));
111+
return stop();
107112
}
108-
on_error(Status::Error(r.error().public_message()));
109-
return stop();
110113
}
111114
}
112115
read_source_.wakeup();
@@ -195,6 +198,12 @@ void HttpConnectionBase::loop() {
195198
}
196199
return stop();
197200
}
201+
202+
if (want_read && can_read_local(fd_)) {
203+
// reading was suspended or truncated because of MAX_PENDING_READ_SIZE, but the parser needs
204+
// more data and the socket may have no new events: return to the connection right away
205+
yield();
206+
}
198207
}
199208

200209
void HttpConnectionBase::on_start_migrate(int32 sched_id) {

tdnet/td/net/HttpConnectionBase.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class HttpConnectionBase : public Actor {
3636
int32 idle_timeout, int32 slow_scheduler_id);
3737

3838
private:
39+
// Maximum size of received data pending in memory. When it is reached, reading from the socket is
40+
// suspended, so that the kernel receive buffer fills up and TCP flow control throttles the peer.
41+
// Must be greater than any size the query parser can request at once (which is at most
42+
// max(HttpReader::MAX_TOTAL_HEADERS_LENGTH, HttpReader::MAX_TOTAL_PARAMETERS_LENGTH) + 1).
43+
static constexpr size_t MAX_PENDING_READ_SIZE = 4 << 20;
44+
3945
State state_;
4046

4147
BufferedFd<SocketFd> fd_;

0 commit comments

Comments
 (0)