Built motion from commit 6a09e18b.|2.6.11
[motion2.git] / legacy-libs / grpc-cloned / deps / grpc / src / core / ext / filters / max_age / max_age_filter.cc
diff --git a/legacy-libs/grpc-cloned/deps/grpc/src/core/ext/filters/max_age/max_age_filter.cc b/legacy-libs/grpc-cloned/deps/grpc/src/core/ext/filters/max_age/max_age_filter.cc
new file mode 100644 (file)
index 0000000..96f2f02
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/ext/filters/max_age/max_age_filter.h"
+
+#include <limits.h>
+#include <string.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/channel/channel_stack_builder.h"
+#include "src/core/lib/iomgr/timer.h"
+#include "src/core/lib/surface/channel_init.h"
+#include "src/core/lib/transport/http2_errors.h"
+
+#define DEFAULT_MAX_CONNECTION_AGE_MS INT_MAX
+#define DEFAULT_MAX_CONNECTION_AGE_GRACE_MS INT_MAX
+#define DEFAULT_MAX_CONNECTION_IDLE_MS INT_MAX
+#define MAX_CONNECTION_AGE_JITTER 0.1
+
+#define MAX_CONNECTION_AGE_INTEGER_OPTIONS \
+  { DEFAULT_MAX_CONNECTION_AGE_MS, 1, INT_MAX }
+#define MAX_CONNECTION_IDLE_INTEGER_OPTIONS \
+  { DEFAULT_MAX_CONNECTION_IDLE_MS, 1, INT_MAX }
+
+/* States for idle_state in channel_data */
+#define MAX_IDLE_STATE_INIT ((gpr_atm)0)
+#define MAX_IDLE_STATE_SEEN_EXIT_IDLE ((gpr_atm)1)
+#define MAX_IDLE_STATE_SEEN_ENTER_IDLE ((gpr_atm)2)
+#define MAX_IDLE_STATE_TIMER_SET ((gpr_atm)3)
+
+namespace {
+struct channel_data {
+  /* The channel stack to which we take refs for pending callbacks. */
+  grpc_channel_stack* channel_stack;
+  /* Guards access to max_age_timer, max_age_timer_pending, max_age_grace_timer
+     and max_age_grace_timer_pending */
+  gpr_mu max_age_timer_mu;
+  /* True if the max_age timer callback is currently pending */
+  bool max_age_timer_pending;
+  /* True if the max_age_grace timer callback is currently pending */
+  bool max_age_grace_timer_pending;
+  /* The timer for checking if the channel has reached its max age */
+  grpc_timer max_age_timer;
+  /* The timer for checking if the max-aged channel has uesed up the grace
+     period */
+  grpc_timer max_age_grace_timer;
+  /* The timer for checking if the channel's idle duration reaches
+     max_connection_idle */
+  grpc_timer max_idle_timer;
+  /* Allowed max time a channel may have no outstanding rpcs */
+  grpc_millis max_connection_idle;
+  /* Allowed max time a channel may exist */
+  grpc_millis max_connection_age;
+  /* Allowed grace period after the channel reaches its max age */
+  grpc_millis max_connection_age_grace;
+  /* Closure to run when the channel's idle duration reaches max_connection_idle
+     and should be closed gracefully */
+  grpc_closure max_idle_timer_cb;
+  /* Closure to run when the channel reaches its max age and should be closed
+     gracefully */
+  grpc_closure close_max_age_channel;
+  /* Closure to run the channel uses up its max age grace time and should be
+     closed forcibly */
+  grpc_closure force_close_max_age_channel;
+  /* Closure to run when the init fo channel stack is done and the max_idle
+     timer should be started */
+  grpc_closure start_max_idle_timer_after_init;
+  /* Closure to run when the init fo channel stack is done and the max_age timer
+     should be started */
+  grpc_closure start_max_age_timer_after_init;
+  /* Closure to run when the goaway op is finished and the max_age_timer */
+  grpc_closure start_max_age_grace_timer_after_goaway_op;
+  /* Closure to run when the channel connectivity state changes */
+  grpc_closure channel_connectivity_changed;
+  /* Records the current connectivity state */
+  grpc_connectivity_state connectivity_state;
+  /* Number of active calls */
+  gpr_atm call_count;
+  /* TODO(zyc): C++lize this state machine */
+  /* 'idle_state' holds the states of max_idle_timer and channel idleness.
+      It can contain one of the following values:
+     +--------------------------------+----------------+---------+
+     |           idle_state           | max_idle_timer | channel |
+     +--------------------------------+----------------+---------+
+     | MAX_IDLE_STATE_INIT            | unset          | busy    |
+     | MAX_IDLE_STATE_TIMER_SET       | set, valid     | idle    |
+     | MAX_IDLE_STATE_SEEN_EXIT_IDLE  | set, invalid   | busy    |
+     | MAX_IDLE_STATE_SEEN_ENTER_IDLE | set, invalid   | idle    |
+     +--------------------------------+----------------+---------+
+
+     MAX_IDLE_STATE_INIT: The initial and final state of 'idle_state'. The
+     channel has 1 or 1+ active calls, and the timer is not set. Note that
+     we may put a virtual call to hold this state at channel initialization or
+     shutdown, so that the channel won't enter other states.
+
+     MAX_IDLE_STATE_TIMER_SET: The state after the timer is set and no calls
+     have arrived after the timer is set. The channel must have 0 active call in
+     this state. If the timer is fired in this state, we will close the channel
+     due to idleness.
+
+     MAX_IDLE_STATE_SEEN_EXIT_IDLE: The state after the timer is set and at
+     least one call has arrived after the timer is set. The channel must have 1
+     or 1+ active calls in this state. If the timer is fired in this state, we
+     won't reschudle it.
+
+     MAX_IDLE_STATE_SEEN_ENTER_IDLE: The state after the timer is set and the at
+     least one call has arrived after the timer is set, BUT the channel
+     currently has 0 active calls. If the timer is fired in this state, we will
+     reschudle it.
+
+     max_idle_timer will not be cancelled (unless the channel is shutting down).
+     If the timer callback is called when the max_idle_timer is valid (i.e.
+     idle_state is MAX_IDLE_STATE_TIMER_SET), the channel will be closed due to
+     idleness, otherwise the channel won't be changed.
+
+     State transitions:
+         MAX_IDLE_STATE_INIT <-------3------ MAX_IDLE_STATE_SEEN_EXIT_IDLE
+              ^    |                              ^     ^    |
+              |    |                              |     |    |
+              1    2     +-----------4------------+     6    7
+              |    |     |                              |    |
+              |    v     |                              |    v
+       MAX_IDLE_STATE_TIMER_SET <----5------ MAX_IDLE_STATE_SEEN_ENTER_IDLE
+
+     For 1, 3, 5 :  See max_idle_timer_cb() function
+     For 2, 7    :  See decrease_call_count() function
+     For 4, 6    :  See increase_call_count() function */
+  gpr_atm idle_state;
+  /* Time when the channel finished its last outstanding call, in grpc_millis */
+  gpr_atm last_enter_idle_time_millis;
+};
+}  // namespace
+
+/* Increase the nubmer of active calls. Before the increasement, if there are no
+   calls, the max_idle_timer should be cancelled. */
+static void increase_call_count(channel_data* chand) {
+  /* Exit idle */
+  if (gpr_atm_full_fetch_add(&chand->call_count, 1) == 0) {
+    while (true) {
+      gpr_atm idle_state = gpr_atm_acq_load(&chand->idle_state);
+      switch (idle_state) {
+        case MAX_IDLE_STATE_TIMER_SET:
+          /* max_idle_timer_cb may have already set idle_state to
+             MAX_IDLE_STATE_INIT, in this case, we don't need to set it to
+             MAX_IDLE_STATE_SEEN_EXIT_IDLE */
+          gpr_atm_rel_cas(&chand->idle_state, MAX_IDLE_STATE_TIMER_SET,
+                          MAX_IDLE_STATE_SEEN_EXIT_IDLE);
+          return;
+        case MAX_IDLE_STATE_SEEN_ENTER_IDLE:
+          gpr_atm_rel_store(&chand->idle_state, MAX_IDLE_STATE_SEEN_EXIT_IDLE);
+          return;
+        default:
+          /* try again */
+          break;
+      }
+    }
+  }
+}
+
+/* Decrease the nubmer of active calls. After the decrement, if there are no
+   calls, the max_idle_timer should be started. */
+static void decrease_call_count(channel_data* chand) {
+  /* Enter idle */
+  if (gpr_atm_full_fetch_add(&chand->call_count, -1) == 1) {
+    gpr_atm_no_barrier_store(&chand->last_enter_idle_time_millis,
+                             (gpr_atm)grpc_core::ExecCtx::Get()->Now());
+    while (true) {
+      gpr_atm idle_state = gpr_atm_acq_load(&chand->idle_state);
+      switch (idle_state) {
+        case MAX_IDLE_STATE_INIT:
+          GRPC_CHANNEL_STACK_REF(chand->channel_stack,
+                                 "max_age max_idle_timer");
+          grpc_timer_init(
+              &chand->max_idle_timer,
+              grpc_core::ExecCtx::Get()->Now() + chand->max_connection_idle,
+              &chand->max_idle_timer_cb);
+          gpr_atm_rel_store(&chand->idle_state, MAX_IDLE_STATE_TIMER_SET);
+          return;
+        case MAX_IDLE_STATE_SEEN_EXIT_IDLE:
+          if (gpr_atm_rel_cas(&chand->idle_state, MAX_IDLE_STATE_SEEN_EXIT_IDLE,
+                              MAX_IDLE_STATE_SEEN_ENTER_IDLE)) {
+            return;
+          }
+          break;
+        default:
+          /* try again */
+          break;
+      }
+    }
+  }
+}
+
+static void start_max_idle_timer_after_init(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  /* Decrease call_count. If there are no active calls at this time,
+     max_idle_timer will start here. If the number of active calls is not 0,
+     max_idle_timer will start after all the active calls end. */
+  decrease_call_count(chand);
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack,
+                           "max_age start_max_idle_timer_after_init");
+}
+
+static void start_max_age_timer_after_init(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  gpr_mu_lock(&chand->max_age_timer_mu);
+  chand->max_age_timer_pending = true;
+  GRPC_CHANNEL_STACK_REF(chand->channel_stack, "max_age max_age_timer");
+  grpc_timer_init(&chand->max_age_timer,
+                  grpc_core::ExecCtx::Get()->Now() + chand->max_connection_age,
+                  &chand->close_max_age_channel);
+  gpr_mu_unlock(&chand->max_age_timer_mu);
+  grpc_transport_op* op = grpc_make_transport_op(nullptr);
+  op->on_connectivity_state_change = &chand->channel_connectivity_changed;
+  op->connectivity_state = &chand->connectivity_state;
+  grpc_channel_next_op(grpc_channel_stack_element(chand->channel_stack, 0), op);
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack,
+                           "max_age start_max_age_timer_after_init");
+}
+
+static void start_max_age_grace_timer_after_goaway_op(void* arg,
+                                                      grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  gpr_mu_lock(&chand->max_age_timer_mu);
+  chand->max_age_grace_timer_pending = true;
+  GRPC_CHANNEL_STACK_REF(chand->channel_stack, "max_age max_age_grace_timer");
+  grpc_timer_init(
+      &chand->max_age_grace_timer,
+      chand->max_connection_age_grace == GRPC_MILLIS_INF_FUTURE
+          ? GRPC_MILLIS_INF_FUTURE
+          : grpc_core::ExecCtx::Get()->Now() + chand->max_connection_age_grace,
+      &chand->force_close_max_age_channel);
+  gpr_mu_unlock(&chand->max_age_timer_mu);
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack,
+                           "max_age start_max_age_grace_timer_after_goaway_op");
+}
+
+static void close_max_idle_channel(channel_data* chand) {
+  /* Prevent the max idle timer from being set again */
+  gpr_atm_no_barrier_fetch_add(&chand->call_count, 1);
+  grpc_transport_op* op = grpc_make_transport_op(nullptr);
+  op->goaway_error =
+      grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING("max_idle"),
+                         GRPC_ERROR_INT_HTTP2_ERROR, GRPC_HTTP2_NO_ERROR);
+  grpc_channel_element* elem =
+      grpc_channel_stack_element(chand->channel_stack, 0);
+  elem->filter->start_transport_op(elem, op);
+}
+
+static void max_idle_timer_cb(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  if (error == GRPC_ERROR_NONE) {
+    bool try_again = true;
+    while (try_again) {
+      gpr_atm idle_state = gpr_atm_acq_load(&chand->idle_state);
+      switch (idle_state) {
+        case MAX_IDLE_STATE_TIMER_SET:
+          close_max_idle_channel(chand);
+          /* This MAX_IDLE_STATE_INIT is a final state, we don't have to check
+           * if idle_state has been changed */
+          gpr_atm_rel_store(&chand->idle_state, MAX_IDLE_STATE_INIT);
+          try_again = false;
+          break;
+        case MAX_IDLE_STATE_SEEN_EXIT_IDLE:
+          if (gpr_atm_rel_cas(&chand->idle_state, MAX_IDLE_STATE_SEEN_EXIT_IDLE,
+                              MAX_IDLE_STATE_INIT)) {
+            try_again = false;
+          }
+          break;
+        case MAX_IDLE_STATE_SEEN_ENTER_IDLE:
+          GRPC_CHANNEL_STACK_REF(chand->channel_stack,
+                                 "max_age max_idle_timer");
+          grpc_timer_init(&chand->max_idle_timer,
+                          static_cast<grpc_millis>(gpr_atm_no_barrier_load(
+                              &chand->last_enter_idle_time_millis)) +
+                              chand->max_connection_idle,
+                          &chand->max_idle_timer_cb);
+          /* idle_state may have already been set to
+             MAX_IDLE_STATE_SEEN_EXIT_IDLE by increase_call_count(), in this
+             case, we don't need to set it to MAX_IDLE_STATE_TIMER_SET */
+          gpr_atm_rel_cas(&chand->idle_state, MAX_IDLE_STATE_SEEN_ENTER_IDLE,
+                          MAX_IDLE_STATE_TIMER_SET);
+          try_again = false;
+          break;
+        default:
+          /* try again */
+          break;
+      }
+    }
+  }
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack, "max_age max_idle_timer");
+}
+
+static void close_max_age_channel(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  gpr_mu_lock(&chand->max_age_timer_mu);
+  chand->max_age_timer_pending = false;
+  gpr_mu_unlock(&chand->max_age_timer_mu);
+  if (error == GRPC_ERROR_NONE) {
+    GRPC_CHANNEL_STACK_REF(chand->channel_stack,
+                           "max_age start_max_age_grace_timer_after_goaway_op");
+    grpc_transport_op* op = grpc_make_transport_op(
+        &chand->start_max_age_grace_timer_after_goaway_op);
+    op->goaway_error =
+        grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING("max_age"),
+                           GRPC_ERROR_INT_HTTP2_ERROR, GRPC_HTTP2_NO_ERROR);
+    grpc_channel_element* elem =
+        grpc_channel_stack_element(chand->channel_stack, 0);
+    elem->filter->start_transport_op(elem, op);
+  } else if (error != GRPC_ERROR_CANCELLED) {
+    GRPC_LOG_IF_ERROR("close_max_age_channel", error);
+  }
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack, "max_age max_age_timer");
+}
+
+static void force_close_max_age_channel(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  gpr_mu_lock(&chand->max_age_timer_mu);
+  chand->max_age_grace_timer_pending = false;
+  gpr_mu_unlock(&chand->max_age_timer_mu);
+  if (error == GRPC_ERROR_NONE) {
+    grpc_transport_op* op = grpc_make_transport_op(nullptr);
+    op->disconnect_with_error =
+        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel reaches max age");
+    grpc_channel_element* elem =
+        grpc_channel_stack_element(chand->channel_stack, 0);
+    elem->filter->start_transport_op(elem, op);
+  } else if (error != GRPC_ERROR_CANCELLED) {
+    GRPC_LOG_IF_ERROR("force_close_max_age_channel", error);
+  }
+  GRPC_CHANNEL_STACK_UNREF(chand->channel_stack, "max_age max_age_grace_timer");
+}
+
+static void channel_connectivity_changed(void* arg, grpc_error* error) {
+  channel_data* chand = static_cast<channel_data*>(arg);
+  if (chand->connectivity_state != GRPC_CHANNEL_SHUTDOWN) {
+    grpc_transport_op* op = grpc_make_transport_op(nullptr);
+    op->on_connectivity_state_change = &chand->channel_connectivity_changed;
+    op->connectivity_state = &chand->connectivity_state;
+    grpc_channel_next_op(grpc_channel_stack_element(chand->channel_stack, 0),
+                         op);
+  } else {
+    gpr_mu_lock(&chand->max_age_timer_mu);
+    if (chand->max_age_timer_pending) {
+      grpc_timer_cancel(&chand->max_age_timer);
+      chand->max_age_timer_pending = false;
+    }
+    if (chand->max_age_grace_timer_pending) {
+      grpc_timer_cancel(&chand->max_age_grace_timer);
+      chand->max_age_grace_timer_pending = false;
+    }
+    gpr_mu_unlock(&chand->max_age_timer_mu);
+    /* If there are no active calls, this increasement will cancel
+       max_idle_timer, and prevent max_idle_timer from being started in the
+       future. */
+    increase_call_count(chand);
+    if (gpr_atm_acq_load(&chand->idle_state) == MAX_IDLE_STATE_SEEN_EXIT_IDLE) {
+      grpc_timer_cancel(&chand->max_idle_timer);
+    }
+  }
+}
+
+/* A random jitter of +/-10% will be added to MAX_CONNECTION_AGE to spread out
+   connection storms. Note that the MAX_CONNECTION_AGE option without jitter
+   would not create connection storms by itself, but if there happened to be a
+   connection storm it could cause it to repeat at a fixed period. */
+static grpc_millis
+add_random_max_connection_age_jitter_and_convert_to_grpc_millis(int value) {
+  /* generate a random number between 1 - MAX_CONNECTION_AGE_JITTER and
+     1 + MAX_CONNECTION_AGE_JITTER */
+  double multiplier = rand() * MAX_CONNECTION_AGE_JITTER * 2.0 / RAND_MAX +
+                      1.0 - MAX_CONNECTION_AGE_JITTER;
+  double result = multiplier * value;
+  /* INT_MAX - 0.5 converts the value to float, so that result will not be
+     cast to int implicitly before the comparison. */
+  return result > (static_cast<double>(GRPC_MILLIS_INF_FUTURE)) - 0.5
+             ? GRPC_MILLIS_INF_FUTURE
+             : static_cast<grpc_millis>(result);
+}
+
+/* Constructor for call_data. */
+static grpc_error* init_call_elem(grpc_call_element* elem,
+                                  const grpc_call_element_args* args) {
+  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
+  increase_call_count(chand);
+  return GRPC_ERROR_NONE;
+}
+
+/* Destructor for call_data. */
+static void destroy_call_elem(grpc_call_element* elem,
+                              const grpc_call_final_info* final_info,
+                              grpc_closure* ignored) {
+  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
+  decrease_call_count(chand);
+}
+
+/* Constructor for channel_data. */
+static grpc_error* init_channel_elem(grpc_channel_element* elem,
+                                     grpc_channel_element_args* args) {
+  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
+  gpr_mu_init(&chand->max_age_timer_mu);
+  chand->max_age_timer_pending = false;
+  chand->max_age_grace_timer_pending = false;
+  chand->channel_stack = args->channel_stack;
+  chand->max_connection_age =
+      add_random_max_connection_age_jitter_and_convert_to_grpc_millis(
+          DEFAULT_MAX_CONNECTION_AGE_MS);
+  chand->max_connection_age_grace =
+      DEFAULT_MAX_CONNECTION_AGE_GRACE_MS == INT_MAX
+          ? GRPC_MILLIS_INF_FUTURE
+          : DEFAULT_MAX_CONNECTION_AGE_GRACE_MS;
+  chand->max_connection_idle = DEFAULT_MAX_CONNECTION_IDLE_MS == INT_MAX
+                                   ? GRPC_MILLIS_INF_FUTURE
+                                   : DEFAULT_MAX_CONNECTION_IDLE_MS;
+  chand->idle_state = MAX_IDLE_STATE_INIT;
+  gpr_atm_no_barrier_store(&chand->last_enter_idle_time_millis, GPR_ATM_MIN);
+  for (size_t i = 0; i < args->channel_args->num_args; ++i) {
+    if (0 == strcmp(args->channel_args->args[i].key,
+                    GRPC_ARG_MAX_CONNECTION_AGE_MS)) {
+      const int value = grpc_channel_arg_get_integer(
+          &args->channel_args->args[i], MAX_CONNECTION_AGE_INTEGER_OPTIONS);
+      chand->max_connection_age =
+          add_random_max_connection_age_jitter_and_convert_to_grpc_millis(
+              value);
+    } else if (0 == strcmp(args->channel_args->args[i].key,
+                           GRPC_ARG_MAX_CONNECTION_AGE_GRACE_MS)) {
+      const int value = grpc_channel_arg_get_integer(
+          &args->channel_args->args[i],
+          {DEFAULT_MAX_CONNECTION_AGE_GRACE_MS, 0, INT_MAX});
+      chand->max_connection_age_grace =
+          value == INT_MAX ? GRPC_MILLIS_INF_FUTURE : value;
+    } else if (0 == strcmp(args->channel_args->args[i].key,
+                           GRPC_ARG_MAX_CONNECTION_IDLE_MS)) {
+      const int value = grpc_channel_arg_get_integer(
+          &args->channel_args->args[i], MAX_CONNECTION_IDLE_INTEGER_OPTIONS);
+      chand->max_connection_idle =
+          value == INT_MAX ? GRPC_MILLIS_INF_FUTURE : value;
+    }
+  }
+  GRPC_CLOSURE_INIT(&chand->max_idle_timer_cb, max_idle_timer_cb, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->close_max_age_channel, close_max_age_channel, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->force_close_max_age_channel,
+                    force_close_max_age_channel, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->start_max_idle_timer_after_init,
+                    start_max_idle_timer_after_init, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->start_max_age_timer_after_init,
+                    start_max_age_timer_after_init, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->start_max_age_grace_timer_after_goaway_op,
+                    start_max_age_grace_timer_after_goaway_op, chand,
+                    grpc_schedule_on_exec_ctx);
+  GRPC_CLOSURE_INIT(&chand->channel_connectivity_changed,
+                    channel_connectivity_changed, chand,
+                    grpc_schedule_on_exec_ctx);
+
+  if (chand->max_connection_age != GRPC_MILLIS_INF_FUTURE) {
+    /* When the channel reaches its max age, we send down an op with
+       goaway_error set.  However, we can't send down any ops until after the
+       channel stack is fully initialized.  If we start the timer here, we have
+       no guarantee that the timer won't pop before channel stack initialization
+       is finished.  To avoid that problem, we create a closure to start the
+       timer, and we schedule that closure to be run after call stack
+       initialization is done. */
+    GRPC_CHANNEL_STACK_REF(chand->channel_stack,
+                           "max_age start_max_age_timer_after_init");
+    GRPC_CLOSURE_SCHED(&chand->start_max_age_timer_after_init, GRPC_ERROR_NONE);
+  }
+
+  /* Initialize the number of calls as 1, so that the max_idle_timer will not
+     start until start_max_idle_timer_after_init is invoked. */
+  gpr_atm_rel_store(&chand->call_count, 1);
+  if (chand->max_connection_idle != GRPC_MILLIS_INF_FUTURE) {
+    GRPC_CHANNEL_STACK_REF(chand->channel_stack,
+                           "max_age start_max_idle_timer_after_init");
+    GRPC_CLOSURE_SCHED(&chand->start_max_idle_timer_after_init,
+                       GRPC_ERROR_NONE);
+  }
+  return GRPC_ERROR_NONE;
+}
+
+/* Destructor for channel_data. */
+static void destroy_channel_elem(grpc_channel_element* elem) {
+  channel_data* chand = static_cast<channel_data*>(elem->channel_data);
+  gpr_mu_destroy(&chand->max_age_timer_mu);
+}
+
+const grpc_channel_filter grpc_max_age_filter = {
+    grpc_call_next_op,
+    grpc_channel_next_op,
+    0, /* sizeof_call_data */
+    init_call_elem,
+    grpc_call_stack_ignore_set_pollset_or_pollset_set,
+    destroy_call_elem,
+    sizeof(channel_data),
+    init_channel_elem,
+    destroy_channel_elem,
+    grpc_channel_next_get_info,
+    "max_age"};
+
+static bool maybe_add_max_age_filter(grpc_channel_stack_builder* builder,
+                                     void* arg) {
+  const grpc_channel_args* channel_args =
+      grpc_channel_stack_builder_get_channel_arguments(builder);
+  bool enable =
+      grpc_channel_arg_get_integer(
+          grpc_channel_args_find(channel_args, GRPC_ARG_MAX_CONNECTION_AGE_MS),
+          MAX_CONNECTION_AGE_INTEGER_OPTIONS) != INT_MAX ||
+      grpc_channel_arg_get_integer(
+          grpc_channel_args_find(channel_args, GRPC_ARG_MAX_CONNECTION_IDLE_MS),
+          MAX_CONNECTION_IDLE_INTEGER_OPTIONS) != INT_MAX;
+  if (enable) {
+    return grpc_channel_stack_builder_prepend_filter(
+        builder, &grpc_max_age_filter, nullptr, nullptr);
+  } else {
+    return true;
+  }
+}
+
+void grpc_max_age_filter_init(void) {
+  grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL,
+                                   GRPC_CHANNEL_INIT_BUILTIN_PRIORITY,
+                                   maybe_add_max_age_filter, nullptr);
+}
+
+void grpc_max_age_filter_shutdown(void) {}