Skip to content

Commit

Permalink
Merge pull request grpc#9902 from y-zeng/keepalive_server
Browse files Browse the repository at this point in the history
Client-side keepalive pings server enforcement
  • Loading branch information
y-zeng committed Apr 5, 2017
2 parents 500e361 + b0c2225 commit 526e624
Show file tree
Hide file tree
Showing 20 changed files with 966 additions and 57 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4386,6 +4386,7 @@ add_library(end2end_tests
test/core/end2end/end2end_test_utils.c
test/core/end2end/tests/authority_not_supported.c
test/core/end2end/tests/bad_hostname.c
test/core/end2end/tests/bad_ping.c
test/core/end2end/tests/binary_metadata.c
test/core/end2end/tests/call_creds.c
test/core/end2end/tests/cancel_after_accept.c
Expand Down Expand Up @@ -4483,6 +4484,7 @@ add_library(end2end_nosec_tests
test/core/end2end/end2end_test_utils.c
test/core/end2end/tests/authority_not_supported.c
test/core/end2end/tests/bad_hostname.c
test/core/end2end/tests/bad_ping.c
test/core/end2end/tests/binary_metadata.c
test/core/end2end/tests/cancel_after_accept.c
test/core/end2end/tests/cancel_after_client_done.c
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8221,6 +8221,7 @@ LIBEND2END_TESTS_SRC = \
test/core/end2end/end2end_test_utils.c \
test/core/end2end/tests/authority_not_supported.c \
test/core/end2end/tests/bad_hostname.c \
test/core/end2end/tests/bad_ping.c \
test/core/end2end/tests/binary_metadata.c \
test/core/end2end/tests/call_creds.c \
test/core/end2end/tests/cancel_after_accept.c \
Expand Down Expand Up @@ -8313,6 +8314,7 @@ LIBEND2END_NOSEC_TESTS_SRC = \
test/core/end2end/end2end_test_utils.c \
test/core/end2end/tests/authority_not_supported.c \
test/core/end2end/tests/bad_hostname.c \
test/core/end2end/tests/bad_ping.c \
test/core/end2end/tests/binary_metadata.c \
test/core/end2end/tests/cancel_after_accept.c \
test/core/end2end/tests/cancel_after_client_done.c \
Expand Down
22 changes: 16 additions & 6 deletions include/grpc/impl/codegen/grpc_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,25 @@ typedef struct {
a data frame or header frame) */
#define GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA \
"grpc.http2.max_pings_without_data"
/** How many misbehaving pings the server can bear before sending goaway and
closing the transport?
(0 indicates that the server can bear an infinite number of misbehaving
pings) */
#define GRPC_ARG_HTTP2_MAX_PING_STRIKES "grpc.http2.max_ping_strikes"
/** Minimum allowed time between two pings without sending any data frame. Int
valued, seconds */
#define GRPC_ARG_HTTP2_MIN_PING_INTERVAL_WITHOUT_DATA_MS \
"grpc.http2.min_ping_interval_without_data_ms"
/** How much data are we willing to queue up per stream if
GRPC_WRITE_BUFFER_HINT is set? This is an upper bound */
#define GRPC_ARG_HTTP2_WRITE_BUFFER_SIZE "grpc.http2.write_buffer_size"
/** After a duration of this time the client pings the server to see if the
transport is still alive. Int valued, seconds. */
#define GRPC_ARG_CLIENT_KEEPALIVE_TIME_S "grpc.client_keepalive_time"
/** After waiting for a duration of this time, if the client does not receive
the ping ack, it will close the transport. Int valued, seconds. */
#define GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S "grpc.client_keepalive_timeout"
/** After a duration of this time the client/server pings its peer to see if the
transport is still alive. Int valued, milliseconds. */
#define GRPC_ARG_KEEPALIVE_TIME_MS "grpc.keepalive_time_ms"
/** After waiting for a duration of this time, if the keepalive ping sender does
not receive the ping ack, it will close the transport. Int valued,
milliseconds. */
#define GRPC_ARG_KEEPALIVE_TIMEOUT_MS "grpc.keepalive_timeout_ms"
/** Is it permissible to send keepalive pings without any outstanding streams.
Int valued, 0(false)/1(true). */
#define GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS \
Expand Down
184 changes: 145 additions & 39 deletions src/core/ext/transport/chttp2/transport/chttp2_transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,21 @@
#define MAX_WRITE_BUFFER_SIZE (64 * 1024 * 1024)
#define DEFAULT_MAX_HEADER_LIST_SIZE (16 * 1024)

#define DEFAULT_CLIENT_KEEPALIVE_TIME_S INT_MAX
#define DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_S 20
#define DEFAULT_CLIENT_KEEPALIVE_TIME_MS INT_MAX
#define DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_MS 20000 /* 20 seconds */
#define DEFAULT_SERVER_KEEPALIVE_TIME_MS 7200000 /* 2 hours */
#define DEFAULT_SERVER_KEEPALIVE_TIMEOUT_MS 20000 /* 20 seconds */
#define DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS false

static int g_default_client_keepalive_time_s = DEFAULT_CLIENT_KEEPALIVE_TIME_S;
static int g_default_client_keepalive_timeout_s =
DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_S;
#define KEEPALIVE_TIME_BACKOFF_MULTIPLIER 2

static int g_default_client_keepalive_time_ms =
DEFAULT_CLIENT_KEEPALIVE_TIME_MS;
static int g_default_client_keepalive_timeout_ms =
DEFAULT_CLIENT_KEEPALIVE_TIMEOUT_MS;
static int g_default_server_keepalive_time_ms =
DEFAULT_SERVER_KEEPALIVE_TIME_MS;
static int g_default_server_keepalive_timeout_ms =
DEFAULT_SERVER_KEEPALIVE_TIMEOUT_MS;
static bool g_default_keepalive_permit_without_calls =
DEFAULT_KEEPALIVE_PERMIT_WITHOUT_CALLS;

Expand Down Expand Up @@ -153,6 +161,8 @@ static void retry_initiate_ping_locked(grpc_exec_ctx *exec_ctx, void *tp,

#define DEFAULT_MIN_TIME_BETWEEN_PINGS_MS 0
#define DEFAULT_MAX_PINGS_BETWEEN_DATA 3
#define DEFAULT_MAX_PING_STRIKES 2
#define DEFAULT_MIN_PING_INTERVAL_WITHOUT_DATA_MS 300000 /* 5 minutes */

/** keepalive-relevant functions */
static void init_keepalive_ping_locked(grpc_exec_ctx *exec_ctx, void *arg,
Expand Down Expand Up @@ -351,19 +361,35 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
.max_pings_without_data = DEFAULT_MAX_PINGS_BETWEEN_DATA,
.min_time_between_pings =
gpr_time_from_millis(DEFAULT_MIN_TIME_BETWEEN_PINGS_MS, GPR_TIMESPAN),
.max_ping_strikes = DEFAULT_MAX_PING_STRIKES,
.min_ping_interval_without_data = gpr_time_from_millis(
DEFAULT_MIN_PING_INTERVAL_WITHOUT_DATA_MS, GPR_TIMESPAN),
};

/* client-side keepalive setting */
t->keepalive_time =
g_default_client_keepalive_time_s == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(g_default_client_keepalive_time_s,
GPR_TIMESPAN);
t->keepalive_timeout =
g_default_client_keepalive_timeout_s == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(g_default_client_keepalive_timeout_s,
GPR_TIMESPAN);
/* Keepalive setting */
if (t->is_client) {
t->keepalive_time =
g_default_client_keepalive_time_ms == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_millis(g_default_client_keepalive_time_ms,
GPR_TIMESPAN);
t->keepalive_timeout =
g_default_client_keepalive_timeout_ms == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_millis(g_default_client_keepalive_timeout_ms,
GPR_TIMESPAN);
} else {
t->keepalive_time =
g_default_server_keepalive_time_ms == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_millis(g_default_server_keepalive_time_ms,
GPR_TIMESPAN);
t->keepalive_timeout =
g_default_server_keepalive_timeout_ms == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_millis(g_default_server_keepalive_timeout_ms,
GPR_TIMESPAN);
}
t->keepalive_permit_without_calls = g_default_keepalive_permit_without_calls;

if (channel_args) {
Expand Down Expand Up @@ -396,6 +422,11 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
t->ping_policy.max_pings_without_data = grpc_channel_arg_get_integer(
&channel_args->args[i],
(grpc_integer_options){DEFAULT_MAX_PINGS_BETWEEN_DATA, 0, INT_MAX});
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_MAX_PING_STRIKES)) {
t->ping_policy.max_ping_strikes = grpc_channel_arg_get_integer(
&channel_args->args[i],
(grpc_integer_options){DEFAULT_MAX_PING_STRIKES, 0, INT_MAX});
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_MIN_TIME_BETWEEN_PINGS_MS)) {
t->ping_policy.min_time_between_pings = gpr_time_from_millis(
Expand All @@ -404,6 +435,15 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
(grpc_integer_options){DEFAULT_MIN_TIME_BETWEEN_PINGS_MS, 0,
INT_MAX}),
GPR_TIMESPAN);
} else if (0 ==
strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_MIN_PING_INTERVAL_WITHOUT_DATA_MS)) {
t->ping_policy.min_ping_interval_without_data = gpr_time_from_millis(
grpc_channel_arg_get_integer(
&channel_args->args[i],
(grpc_integer_options){
DEFAULT_MIN_PING_INTERVAL_WITHOUT_DATA_MS, 0, INT_MAX}),
GPR_TIMESPAN);
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_HTTP2_WRITE_BUFFER_SIZE)) {
t->write_buffer_size = (uint32_t)grpc_channel_arg_get_integer(
Expand All @@ -414,23 +454,27 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
t->enable_bdp_probe = grpc_channel_arg_get_integer(
&channel_args->args[i], (grpc_integer_options){1, 0, 1});
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_CLIENT_KEEPALIVE_TIME_S)) {
GRPC_ARG_KEEPALIVE_TIME_MS)) {
const int value = grpc_channel_arg_get_integer(
&channel_args->args[i],
(grpc_integer_options){g_default_client_keepalive_time_s, 1,
INT_MAX});
(grpc_integer_options){t->is_client
? g_default_client_keepalive_time_ms
: g_default_server_keepalive_time_ms,
1, INT_MAX});
t->keepalive_time = value == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(value, GPR_TIMESPAN);
: gpr_time_from_millis(value, GPR_TIMESPAN);
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S)) {
GRPC_ARG_KEEPALIVE_TIMEOUT_MS)) {
const int value = grpc_channel_arg_get_integer(
&channel_args->args[i],
(grpc_integer_options){g_default_client_keepalive_timeout_s, 0,
INT_MAX});
(grpc_integer_options){t->is_client
? g_default_client_keepalive_timeout_ms
: g_default_server_keepalive_timeout_ms,
0, INT_MAX});
t->keepalive_timeout = value == INT_MAX
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_seconds(value, GPR_TIMESPAN);
: gpr_time_from_millis(value, GPR_TIMESPAN);
} else if (0 == strcmp(channel_args->args[i].key,
GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS)) {
t->keepalive_permit_without_calls =
Expand Down Expand Up @@ -488,8 +532,11 @@ static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
t->ping_policy.max_pings_without_data;
t->ping_state.is_delayed_ping_timer_set = false;

/** Start client-side keepalive pings */
if (t->is_client) {
t->ping_recv_state.last_ping_recv_time = gpr_inf_past(GPR_CLOCK_MONOTONIC);
t->ping_recv_state.ping_strikes = 0;

/* Start keepalive pings */
if (gpr_time_cmp(t->keepalive_time, gpr_inf_future(GPR_TIMESPAN)) != 0) {
t->keepalive_state = GRPC_CHTTP2_KEEPALIVE_STATE_WAITING;
GRPC_CHTTP2_REF_TRANSPORT(t, "init keepalive ping");
grpc_timer_init(
Expand Down Expand Up @@ -918,6 +965,26 @@ void grpc_chttp2_add_incoming_goaway(grpc_exec_ctx *exec_ctx,
// GRPC_CHTTP2_IF_TRACING(
// gpr_log(GPR_DEBUG, "got goaway [%d]: %s", goaway_error, msg));
t->seen_goaway = 1;

/* When a client receives a GOAWAY with error code ENHANCE_YOUR_CALM and debug
* data equal to “too_many_pings”, it should log the occurrence at a log level
* that is enabled by default and double the configured KEEPALIVE_TIME used
* for new connections on that channel. */
if (t->is_client && goaway_error == GRPC_HTTP2_ENHANCE_YOUR_CALM &&
grpc_slice_str_cmp(goaway_text, "too_many_pings") == 0) {
gpr_log(GPR_ERROR,
"Received a GOAWAY with error code ENHANCE_YOUR_CALM and debug "
"data equal to \"too_many_pings\"");
double current_keepalive_time_ms =
gpr_timespec_to_micros(t->keepalive_time) / 1000;
t->keepalive_time =
current_keepalive_time_ms > INT_MAX / KEEPALIVE_TIME_BACKOFF_MULTIPLIER
? gpr_inf_future(GPR_TIMESPAN)
: gpr_time_from_millis((int64_t)(current_keepalive_time_ms *
KEEPALIVE_TIME_BACKOFF_MULTIPLIER),
GPR_TIMESPAN);
}

/* lie: use transient failure from the transport to indicate goaway has been
* received */
connectivity_state_set(
Expand Down Expand Up @@ -1459,6 +1526,21 @@ static void send_goaway(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
GRPC_ERROR_UNREF(error);
}

void grpc_chttp2_add_ping_strike(grpc_exec_ctx *exec_ctx,
grpc_chttp2_transport *t) {
gpr_log(GPR_DEBUG, "PING strike");
if (++t->ping_recv_state.ping_strikes > t->ping_policy.max_ping_strikes &&
t->ping_policy.max_ping_strikes != 0) {
send_goaway(exec_ctx, t,
grpc_error_set_int(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("too_many_pings"),
GRPC_ERROR_INT_HTTP2_ERROR, GRPC_HTTP2_ENHANCE_YOUR_CALM));
/*The transport will be closed after the write is done */
close_transport_locked(
exec_ctx, t, GRPC_ERROR_CREATE_FROM_STATIC_STRING("Too many pings"));
}
}

static void perform_transport_op_locked(grpc_exec_ctx *exec_ctx,
void *stream_op,
grpc_error *error_ignored) {
Expand Down Expand Up @@ -2125,6 +2207,10 @@ static void start_bdp_ping_locked(grpc_exec_ctx *exec_ctx, void *tp,
if (grpc_http_trace) {
gpr_log(GPR_DEBUG, "%s: Start BDP ping", t->peer_string);
}
/* Reset the keepalive ping timer */
if (t->keepalive_state == GRPC_CHTTP2_KEEPALIVE_STATE_WAITING) {
grpc_timer_cancel(exec_ctx, &t->keepalive_ping_timer);
}
grpc_bdp_estimator_start_ping(&t->bdp_estimator);
}

Expand All @@ -2139,20 +2225,32 @@ static void finish_bdp_ping_locked(grpc_exec_ctx *exec_ctx, void *tp,
GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "bdp_ping");
}

void grpc_chttp2_config_default_keepalive_args(grpc_channel_args *args) {
void grpc_chttp2_config_default_keepalive_args(grpc_channel_args *args,
bool is_client) {
size_t i;
if (args) {
for (i = 0; i < args->num_args; i++) {
if (0 == strcmp(args->args[i].key, GRPC_ARG_CLIENT_KEEPALIVE_TIME_S)) {
g_default_client_keepalive_time_s = grpc_channel_arg_get_integer(
&args->args[i], (grpc_integer_options){
g_default_client_keepalive_time_s, 1, INT_MAX});
} else if (0 == strcmp(args->args[i].key,
GRPC_ARG_CLIENT_KEEPALIVE_TIMEOUT_S)) {
g_default_client_keepalive_timeout_s = grpc_channel_arg_get_integer(
if (0 == strcmp(args->args[i].key, GRPC_ARG_KEEPALIVE_TIME_MS)) {
const int value = grpc_channel_arg_get_integer(
&args->args[i],
(grpc_integer_options){g_default_client_keepalive_time_ms, 1,
INT_MAX});
if (is_client) {
g_default_client_keepalive_time_ms = value;
} else {
g_default_server_keepalive_time_ms = value;
}
} else if (0 ==
strcmp(args->args[i].key, GRPC_ARG_KEEPALIVE_TIMEOUT_MS)) {
const int value = grpc_channel_arg_get_integer(
&args->args[i],
(grpc_integer_options){g_default_client_keepalive_timeout_s, 0,
(grpc_integer_options){g_default_client_keepalive_timeout_ms, 0,
INT_MAX});
if (is_client) {
g_default_client_keepalive_timeout_ms = value;
} else {
g_default_server_keepalive_timeout_ms = value;
}
} else if (0 == strcmp(args->args[i].key,
GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS)) {
g_default_keepalive_permit_without_calls =
Expand All @@ -2170,7 +2268,8 @@ static void init_keepalive_ping_locked(grpc_exec_ctx *exec_ctx, void *arg,
grpc_chttp2_transport *t = arg;
GPR_ASSERT(t->keepalive_state == GRPC_CHTTP2_KEEPALIVE_STATE_WAITING);
if (error == GRPC_ERROR_NONE && !(t->destroying || t->closed)) {
if (t->keepalive_permit_without_calls || t->stream_map.count > 0) {
if (t->keepalive_permit_without_calls ||
grpc_chttp2_stream_map_size(&t->stream_map) > 0) {
t->keepalive_state = GRPC_CHTTP2_KEEPALIVE_STATE_PINGING;
GRPC_CHTTP2_REF_TRANSPORT(t, "keepalive ping end");
send_ping_locked(exec_ctx, t, GRPC_CHTTP2_PING_ON_NEXT_WRITE,
Expand All @@ -2183,6 +2282,13 @@ static void init_keepalive_ping_locked(grpc_exec_ctx *exec_ctx, void *arg,
gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), t->keepalive_time),
&t->init_keepalive_ping_locked, gpr_now(GPR_CLOCK_MONOTONIC));
}
} else if (error == GRPC_ERROR_CANCELLED && !(t->destroying || t->closed)) {
/* The keepalive ping timer may be cancelled by bdp */
GRPC_CHTTP2_REF_TRANSPORT(t, "init keepalive ping");
grpc_timer_init(
exec_ctx, &t->keepalive_ping_timer,
gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), t->keepalive_time),
&t->init_keepalive_ping_locked, gpr_now(GPR_CLOCK_MONOTONIC));
}
GRPC_CHTTP2_UNREF_TRANSPORT(exec_ctx, t, "init keepalive ping");
}
Expand Down Expand Up @@ -2224,8 +2330,8 @@ static void keepalive_watchdog_fired_locked(grpc_exec_ctx *exec_ctx, void *arg,
"keepalive watchdog timeout"));
}
} else {
/** The watchdog timer should have been cancelled by
finish_keepalive_ping_locked. */
/* The watchdog timer should have been cancelled by
* finish_keepalive_ping_locked. */
if (error != GRPC_ERROR_CANCELLED) {
gpr_log(GPR_ERROR, "keepalive_ping_end state error: %d (expect: %d)",
t->keepalive_state, GRPC_CHTTP2_KEEPALIVE_STATE_PINGING);
Expand Down
22 changes: 22 additions & 0 deletions src/core/ext/transport/chttp2/transport/frame_ping.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ grpc_error *grpc_chttp2_ping_parser_parse(grpc_exec_ctx *exec_ctx, void *parser,
if (p->is_ack) {
grpc_chttp2_ack_ping(exec_ctx, t, p->opaque_8bytes);
} else {
if (!t->is_client) {
gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
gpr_timespec next_allowed_ping =
gpr_time_add(t->ping_recv_state.last_ping_recv_time,
t->ping_policy.min_ping_interval_without_data);

if (t->keepalive_permit_without_calls == 0 &&
grpc_chttp2_stream_map_size(&t->stream_map) == 0) {
/* According to RFC1122, the interval of TCP Keep-Alive is default to
no less than two hours. When there is no outstanding streams, we
restrict the number of PINGS equivalent to TCP Keep-Alive. */
next_allowed_ping =
gpr_time_add(t->ping_recv_state.last_ping_recv_time,
gpr_time_from_seconds(7200, GPR_TIMESPAN));
}

if (gpr_time_cmp(next_allowed_ping, now) > 0) {
grpc_chttp2_add_ping_strike(exec_ctx, t);
}

t->ping_recv_state.last_ping_recv_time = now;
}
if (!g_disable_ping_ack) {
if (t->ping_ack_count == t->ping_ack_capacity) {
t->ping_ack_capacity = GPR_MAX(t->ping_ack_capacity * 3 / 2, 3);
Expand Down
Loading

0 comments on commit 526e624

Please sign in to comment.