Skip to content

Commit a88ee9e

Browse files
yashyktcopybara-github
authored andcommitted
[http2] Add experiment to modify behavior of GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA to throttle pings instead of blocking (#36374)
Implements grpc/proposal#429 Currently, the behavior of `GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA` blocks more pings from being sent if we are sending too many pings without a data/header frame being sent as well. The original intention of this channel arg was to play nice with proxies that have restrictive settings when it comes to pings. This causes awkwardness when configuring keepalive pings for transports with long lived streams with sparse communication. In such a case, gRPC Core would stop sending keepalive pings since no data/header frame is being sent, resulting in a situation where we are unable to detect whether the transport is alive or not. This change adds an experiment "max_pings_wo_data_throttle" to modify the behavior of `GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA` to throttle pings to a frequency of 1 minute instead of completely blocking pings when too many pings have been sent without data/header frames. Closes #36374 COPYBARA_INTEGRATE_REVIEW=#36374 from yashykt:ThrottlePings b5bd42a PiperOrigin-RevId: 638110795
1 parent 7a13142 commit a88ee9e

File tree

10 files changed

+120
-20
lines changed

10 files changed

+120
-20
lines changed

bazel/experiments.bzl

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

include/grpc/impl/channel_arg_names.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@
111111
"grpc.server_max_unrequested_time_in_server"
112112
/** Channel arg to override the http2 :scheme header */
113113
#define GRPC_ARG_HTTP2_SCHEME "grpc.http2_scheme"
114-
/** How many pings can the client send before needing to send a
115-
data/header frame? (0 indicates that an infinite number of
116-
pings can be sent without sending a data frame or header frame) */
114+
/** How many pings can the client send before needing to send a data/header
115+
frame? (0 indicates that an infinite number of pings can be sent without
116+
sending a data frame or header frame).
117+
If experiment "max_pings_wo_data_throttle" is enabled, instead of pings being
118+
completely blocked, they are throttled. */
117119
#define GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA \
118120
"grpc.http2.max_pings_without_data"
119121
/** How many misbehaving pings the server can bear before sending goaway and

src/core/ext/transport/chttp2/transport/ping_rate_policy.cc

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,18 @@
3535
namespace grpc_core {
3636

3737
namespace {
38-
int g_default_max_pings_without_data = 2;
38+
int g_default_max_pings_without_data_sent = 2;
39+
constexpr Duration kThrottleIntervalWithoutDataSent = Duration::Minutes(1);
3940
absl::optional<int> g_default_max_inflight_pings;
4041
} // namespace
4142

4243
Chttp2PingRatePolicy::Chttp2PingRatePolicy(const ChannelArgs& args,
4344
bool is_client)
44-
: max_pings_without_data_(
45+
: max_pings_without_data_sent_(
4546
is_client
46-
? std::max(0, args.GetInt(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA)
47-
.value_or(g_default_max_pings_without_data))
47+
? std::max(0,
48+
args.GetInt(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA)
49+
.value_or(g_default_max_pings_without_data_sent))
4850
: 0),
4951
// Configuration via channel arg dominates, otherwise if the multiping
5052
// experiment is enabled we use 100, otherwise 1.
@@ -54,18 +56,15 @@ Chttp2PingRatePolicy::Chttp2PingRatePolicy(const ChannelArgs& args,
5456
IsMultipingEnabled() ? 100 : 1)))) {}
5557

5658
void Chttp2PingRatePolicy::SetDefaults(const ChannelArgs& args) {
57-
g_default_max_pings_without_data =
59+
g_default_max_pings_without_data_sent =
5860
std::max(0, args.GetInt(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA)
59-
.value_or(g_default_max_pings_without_data));
61+
.value_or(g_default_max_pings_without_data_sent));
6062
g_default_max_inflight_pings = args.GetInt(GRPC_ARG_HTTP2_MAX_INFLIGHT_PINGS);
6163
}
6264

6365
Chttp2PingRatePolicy::RequestSendPingResult
6466
Chttp2PingRatePolicy::RequestSendPing(Duration next_allowed_ping_interval,
6567
size_t inflight_pings) const {
66-
if (max_pings_without_data_ != 0 && pings_before_data_required_ == 0) {
67-
return TooManyRecentPings{};
68-
}
6968
if (max_inflight_pings_ > 0 &&
7069
inflight_pings > static_cast<size_t>(max_inflight_pings_)) {
7170
return TooManyRecentPings{};
@@ -77,26 +76,43 @@ Chttp2PingRatePolicy::RequestSendPing(Duration next_allowed_ping_interval,
7776
return TooSoon{next_allowed_ping_interval, last_ping_sent_time_,
7877
next_allowed_ping - now};
7978
}
79+
// Throttle pings to 1 minute if we haven't sent any data recently
80+
if (max_pings_without_data_sent_ != 0 &&
81+
pings_before_data_sending_required_ == 0) {
82+
if (IsMaxPingsWoDataThrottleEnabled()) {
83+
const Timestamp next_allowed_ping =
84+
last_ping_sent_time_ + kThrottleIntervalWithoutDataSent;
85+
if (next_allowed_ping > now) {
86+
return TooSoon{kThrottleIntervalWithoutDataSent, last_ping_sent_time_,
87+
next_allowed_ping - now};
88+
}
89+
} else {
90+
return TooManyRecentPings{};
91+
}
92+
}
93+
8094
return SendGranted{};
8195
}
8296

8397
void Chttp2PingRatePolicy::SentPing() {
8498
last_ping_sent_time_ = Timestamp::Now();
85-
if (pings_before_data_required_) --pings_before_data_required_;
99+
if (pings_before_data_sending_required_ > 0) {
100+
--pings_before_data_sending_required_;
101+
}
86102
}
87103

88104
void Chttp2PingRatePolicy::ReceivedDataFrame() {
89105
last_ping_sent_time_ = Timestamp::InfPast();
90106
}
91107

92108
void Chttp2PingRatePolicy::ResetPingsBeforeDataRequired() {
93-
pings_before_data_required_ = max_pings_without_data_;
109+
pings_before_data_sending_required_ = max_pings_without_data_sent_;
94110
}
95111

96112
std::string Chttp2PingRatePolicy::GetDebugString() const {
97113
return absl::StrCat(
98-
"max_pings_without_data: ", max_pings_without_data_,
99-
", pings_before_data_required: ", pings_before_data_required_,
114+
"max_pings_without_data: ", max_pings_without_data_sent_,
115+
", pings_before_data_required: ", pings_before_data_sending_required_,
100116
", last_ping_sent_time_: ", last_ping_sent_time_.ToString());
101117
}
102118

src/core/ext/transport/chttp2/transport/ping_rate_policy.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@ class Chttp2PingRatePolicy {
7070
void ReceivedDataFrame();
7171
std::string GetDebugString() const;
7272

73-
int TestOnlyMaxPingsWithoutData() const { return max_pings_without_data_; }
73+
int TestOnlyMaxPingsWithoutData() const {
74+
return max_pings_without_data_sent_;
75+
}
7476

7577
private:
76-
const int max_pings_without_data_;
78+
const int max_pings_without_data_sent_;
7779
const int max_inflight_pings_;
78-
// No pings allowed before receiving a header or data frame.
79-
int pings_before_data_required_ = 0;
80+
int pings_before_data_sending_required_ = 0;
8081
Timestamp last_ping_sent_time_ = Timestamp::InfPast();
8182
};
8283

src/core/lib/experiments/experiments.cc

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/lib/experiments/experiments.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/lib/experiments/experiments.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@
120120
owner: yashkt@google.com
121121
test_tags: []
122122
allow_in_fuzzing_config: false
123+
- name: max_pings_wo_data_throttle
124+
description:
125+
Experiment to throttle pings to a period of 1 min when
126+
GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA limit has reached (instead of
127+
completely blocking).
128+
expiry: 2024/12/31
129+
owner: yashkt@google.com
130+
test_tags: []
123131
- name: monitoring_experiment
124132
description: Placeholder experiment to prove/disprove our monitoring is working
125133
expiry: never-ever

src/core/lib/experiments/rollouts.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
default: false
8080
- name: keepalive_server_fix
8181
default: false
82+
- name: max_pings_wo_data_throttle
83+
default: false
8284
- name: monitoring_experiment
8385
default: true
8486
- name: peer_state_based_framing

test/core/end2end/tests/bad_ping.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ CORE_END2END_TEST(RetryHttp2Test, BadPing) {
8787
// Try sending more pings than server allows, but server should be fine because
8888
// max_pings_without_data should limit pings sent out on wire.
8989
CORE_END2END_TEST(RetryHttp2Test, PingsWithoutData) {
90+
if (IsMaxPingsWoDataThrottleEnabled()) {
91+
GTEST_SKIP() << "pings are not limited if this experiment is enabled";
92+
}
9093
// Only allow MAX_PING_STRIKES pings without data (DATA/HEADERS/WINDOW_UPDATE)
9194
// so that the transport will throttle the excess pings.
9295
InitClient(ChannelArgs()

test/core/transport/chttp2/ping_rate_policy_test.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include "gtest/gtest.h"
2121

22+
#include "src/core/lib/experiments/experiments.h"
23+
2224
namespace grpc_core {
2325
namespace {
2426

@@ -47,6 +49,10 @@ TEST(PingRatePolicy, ServerCanSendAtStart) {
4749
}
4850

4951
TEST(PingRatePolicy, ClientBlockedUntilDataSent) {
52+
if (IsMaxPingsWoDataThrottleEnabled()) {
53+
GTEST_SKIP()
54+
<< "Pings are not blocked if max_pings_wo_data_throttle is enabled.";
55+
}
5056
Chttp2PingRatePolicy policy{ChannelArgs(), true};
5157
EXPECT_EQ(policy.RequestSendPing(Duration::Milliseconds(10), 0),
5258
TooManyRecentPings());
@@ -59,6 +65,35 @@ TEST(PingRatePolicy, ClientBlockedUntilDataSent) {
5965
EXPECT_EQ(policy.RequestSendPing(Duration::Zero(), 0), TooManyRecentPings());
6066
}
6167

68+
TEST(PingRatePolicy, ClientThrottledUntilDataSent) {
69+
if (!IsMaxPingsWoDataThrottleEnabled()) {
70+
GTEST_SKIP()
71+
<< "Throttling behavior is enabled with max_pings_wo_data_throttle.";
72+
}
73+
Chttp2PingRatePolicy policy{ChannelArgs(), true};
74+
// First ping is allowed.
75+
EXPECT_EQ(policy.RequestSendPing(Duration::Milliseconds(10), 0),
76+
SendGranted());
77+
policy.SentPing();
78+
// Second ping is throttled since no data has been sent.
79+
auto result = policy.RequestSendPing(Duration::Zero(), 0);
80+
EXPECT_TRUE(absl::holds_alternative<Chttp2PingRatePolicy::TooSoon>(result));
81+
EXPECT_EQ(absl::get<Chttp2PingRatePolicy::TooSoon>(result).wait,
82+
Duration::Minutes(1));
83+
policy.ResetPingsBeforeDataRequired();
84+
// After resetting pings before data required (data sent), we can send pings
85+
// without being throttled.
86+
EXPECT_EQ(policy.RequestSendPing(Duration::Zero(), 0), SendGranted());
87+
policy.SentPing();
88+
EXPECT_EQ(policy.RequestSendPing(Duration::Zero(), 0), SendGranted());
89+
policy.SentPing();
90+
// After reaching limit, we are throttled again.
91+
result = policy.RequestSendPing(Duration::Zero(), 0);
92+
EXPECT_TRUE(absl::holds_alternative<Chttp2PingRatePolicy::TooSoon>(result));
93+
EXPECT_EQ(absl::get<Chttp2PingRatePolicy::TooSoon>(result).wait,
94+
Duration::Minutes(1));
95+
}
96+
6297
TEST(PingRatePolicy, RateThrottlingWorks) {
6398
Chttp2PingRatePolicy policy{ChannelArgs(), false};
6499
// Observe that we can fail if we send in a tight loop

0 commit comments

Comments
 (0)