|  | @@ -0,0 +1,302 @@
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// Copyright 2016, Google Inc.
 | 
	
		
			
				|  |  | +// All rights reserved.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// Redistribution and use in source and binary forms, with or without
 | 
	
		
			
				|  |  | +// modification, are permitted provided that the following conditions are
 | 
	
		
			
				|  |  | +// met:
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +//     * Redistributions of source code must retain the above copyright
 | 
	
		
			
				|  |  | +// notice, this list of conditions and the following disclaimer.
 | 
	
		
			
				|  |  | +//     * Redistributions in binary form must reproduce the above
 | 
	
		
			
				|  |  | +// copyright notice, this list of conditions and the following disclaimer
 | 
	
		
			
				|  |  | +// in the documentation and/or other materials provided with the
 | 
	
		
			
				|  |  | +// distribution.
 | 
	
		
			
				|  |  | +//     * Neither the name of Google Inc. nor the names of its
 | 
	
		
			
				|  |  | +// contributors may be used to endorse or promote products derived from
 | 
	
		
			
				|  |  | +// this software without specific prior written permission.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
	
		
			
				|  |  | +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
	
		
			
				|  |  | +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
	
		
			
				|  |  | +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
	
		
			
				|  |  | +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
	
		
			
				|  |  | +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
	
		
			
				|  |  | +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
	
		
			
				|  |  | +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
	
		
			
				|  |  | +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
	
		
			
				|  |  | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
	
		
			
				|  |  | +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include "src/core/lib/channel/deadline_filter.h"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include <stdbool.h>
 | 
	
		
			
				|  |  | +#include <string.h>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include <grpc/support/alloc.h>
 | 
	
		
			
				|  |  | +#include <grpc/support/log.h>
 | 
	
		
			
				|  |  | +#include <grpc/support/sync.h>
 | 
	
		
			
				|  |  | +#include <grpc/support/time.h>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include "src/core/lib/iomgr/exec_ctx.h"
 | 
	
		
			
				|  |  | +#include "src/core/lib/iomgr/timer.h"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// grpc_deadline_state
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Timer callback.
 | 
	
		
			
				|  |  | +static void timer_callback(grpc_exec_ctx* exec_ctx, void* arg,
 | 
	
		
			
				|  |  | +                           grpc_error* error) {
 | 
	
		
			
				|  |  | +  grpc_call_element* elem = arg;
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = elem->call_data;
 | 
	
		
			
				|  |  | +  gpr_mu_lock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +  deadline_state->timer_pending = false;
 | 
	
		
			
				|  |  | +  gpr_mu_unlock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +  if (error != GRPC_ERROR_CANCELLED) {
 | 
	
		
			
				|  |  | +    gpr_slice msg = gpr_slice_from_static_string("Deadline Exceeded");
 | 
	
		
			
				|  |  | +    grpc_call_element_send_cancel_with_message(
 | 
	
		
			
				|  |  | +        exec_ctx, elem, GRPC_STATUS_DEADLINE_EXCEEDED, &msg);
 | 
	
		
			
				|  |  | +    gpr_slice_unref(msg);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  GRPC_CALL_STACK_UNREF(exec_ctx, deadline_state->call_stack, "deadline_timer");
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Starts the deadline timer.
 | 
	
		
			
				|  |  | +static void start_timer_if_needed(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                  grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                                  gpr_timespec deadline) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = elem->call_data;
 | 
	
		
			
				|  |  | +  deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC);
 | 
	
		
			
				|  |  | +  if (gpr_time_cmp(deadline, gpr_inf_future(GPR_CLOCK_MONOTONIC)) != 0) {
 | 
	
		
			
				|  |  | +    // Take a reference to the call stack, to be owned by the timer.
 | 
	
		
			
				|  |  | +    GRPC_CALL_STACK_REF(deadline_state->call_stack, "deadline_timer");
 | 
	
		
			
				|  |  | +    gpr_mu_lock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +    deadline_state->timer_pending = true;
 | 
	
		
			
				|  |  | +    grpc_timer_init(exec_ctx, &deadline_state->timer, deadline, timer_callback,
 | 
	
		
			
				|  |  | +                    elem, gpr_now(GPR_CLOCK_MONOTONIC));
 | 
	
		
			
				|  |  | +    gpr_mu_unlock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Cancels the deadline timer.
 | 
	
		
			
				|  |  | +static void cancel_timer_if_needed(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                   grpc_deadline_state* deadline_state) {
 | 
	
		
			
				|  |  | +  gpr_mu_lock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +  if (deadline_state->timer_pending) {
 | 
	
		
			
				|  |  | +    grpc_timer_cancel(exec_ctx, &deadline_state->timer);
 | 
	
		
			
				|  |  | +    deadline_state->timer_pending = false;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  gpr_mu_unlock(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Callback run when the call is complete.
 | 
	
		
			
				|  |  | +static void on_complete(grpc_exec_ctx* exec_ctx, void* arg, grpc_error* error) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = arg;
 | 
	
		
			
				|  |  | +  cancel_timer_if_needed(exec_ctx, deadline_state);
 | 
	
		
			
				|  |  | +  // Invoke the next callback.
 | 
	
		
			
				|  |  | +  deadline_state->next_on_complete->cb(
 | 
	
		
			
				|  |  | +      exec_ctx, deadline_state->next_on_complete->cb_arg, error);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Inject our own on_complete callback into op.
 | 
	
		
			
				|  |  | +static void inject_on_complete_cb(grpc_deadline_state* deadline_state,
 | 
	
		
			
				|  |  | +                                  grpc_transport_stream_op* op) {
 | 
	
		
			
				|  |  | +  deadline_state->next_on_complete = op->on_complete;
 | 
	
		
			
				|  |  | +  grpc_closure_init(&deadline_state->on_complete, on_complete, deadline_state);
 | 
	
		
			
				|  |  | +  op->on_complete = &deadline_state->on_complete;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Callback and associated state for starting the timer after call stack
 | 
	
		
			
				|  |  | +// initialization has been completed.
 | 
	
		
			
				|  |  | +struct start_timer_after_init_state {
 | 
	
		
			
				|  |  | +  grpc_call_element* elem;
 | 
	
		
			
				|  |  | +  gpr_timespec deadline;
 | 
	
		
			
				|  |  | +  grpc_closure closure;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +static void start_timer_after_init(grpc_exec_ctx* exec_ctx, void* arg,
 | 
	
		
			
				|  |  | +                                   grpc_error* error) {
 | 
	
		
			
				|  |  | +  struct start_timer_after_init_state* state = arg;
 | 
	
		
			
				|  |  | +  start_timer_if_needed(exec_ctx, state->elem, state->deadline);
 | 
	
		
			
				|  |  | +  gpr_free(state);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void grpc_deadline_state_init(grpc_exec_ctx* exec_ctx, grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                              grpc_call_element_args* args) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = elem->call_data;
 | 
	
		
			
				|  |  | +  memset(deadline_state, 0, sizeof(*deadline_state));
 | 
	
		
			
				|  |  | +  deadline_state->call_stack = args->call_stack;
 | 
	
		
			
				|  |  | +  gpr_mu_init(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +  // Deadline will always be infinite on servers, so the timer will only be
 | 
	
		
			
				|  |  | +  // set on clients with a finite deadline.
 | 
	
		
			
				|  |  | +  const gpr_timespec deadline =
 | 
	
		
			
				|  |  | +      gpr_convert_clock_type(args->deadline, GPR_CLOCK_MONOTONIC);
 | 
	
		
			
				|  |  | +  if (gpr_time_cmp(deadline, gpr_inf_future(GPR_CLOCK_MONOTONIC)) != 0) {
 | 
	
		
			
				|  |  | +    // When the deadline passes, we indicate the failure by sending down
 | 
	
		
			
				|  |  | +    // an op with cancel_error set.  However, we can't send down any ops
 | 
	
		
			
				|  |  | +    // until after the call stack is fully initialized.  If we start the
 | 
	
		
			
				|  |  | +    // timer here, we have no guarantee that the timer won't pop before
 | 
	
		
			
				|  |  | +    // call 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.
 | 
	
		
			
				|  |  | +    struct start_timer_after_init_state* state = gpr_malloc(sizeof(*state));
 | 
	
		
			
				|  |  | +    state->elem = elem;
 | 
	
		
			
				|  |  | +    state->deadline = deadline;
 | 
	
		
			
				|  |  | +    grpc_closure_init(&state->closure, start_timer_after_init, state);
 | 
	
		
			
				|  |  | +    grpc_exec_ctx_sched(exec_ctx, &state->closure, GRPC_ERROR_NONE, NULL);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void grpc_deadline_state_destroy(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                 grpc_call_element* elem) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = elem->call_data;
 | 
	
		
			
				|  |  | +  cancel_timer_if_needed(exec_ctx, deadline_state);
 | 
	
		
			
				|  |  | +  gpr_mu_destroy(&deadline_state->timer_mu);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void grpc_deadline_state_client_start_transport_stream_op(
 | 
	
		
			
				|  |  | +    grpc_exec_ctx* exec_ctx, grpc_call_element* elem,
 | 
	
		
			
				|  |  | +    grpc_transport_stream_op* op) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state* deadline_state = elem->call_data;
 | 
	
		
			
				|  |  | +  if (op->cancel_error != GRPC_ERROR_NONE ||
 | 
	
		
			
				|  |  | +      op->close_error != GRPC_ERROR_NONE) {
 | 
	
		
			
				|  |  | +    cancel_timer_if_needed(exec_ctx, deadline_state);
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    // Make sure we know when the call is complete, so that we can cancel
 | 
	
		
			
				|  |  | +    // the timer.
 | 
	
		
			
				|  |  | +    if (op->recv_trailing_metadata != NULL) {
 | 
	
		
			
				|  |  | +      inject_on_complete_cb(deadline_state, op);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// filter code
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Constructor for channel_data.  Used for both client and server filters.
 | 
	
		
			
				|  |  | +static void init_channel_elem(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                              grpc_channel_element* elem,
 | 
	
		
			
				|  |  | +                              grpc_channel_element_args* args) {
 | 
	
		
			
				|  |  | +  GPR_ASSERT(!args->is_last);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Destructor for channel_data.  Used for both client and server filters.
 | 
	
		
			
				|  |  | +static void destroy_channel_elem(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                 grpc_channel_element* elem) {}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Call data used for both client and server filter.
 | 
	
		
			
				|  |  | +typedef struct base_call_data {
 | 
	
		
			
				|  |  | +  grpc_deadline_state deadline_state;
 | 
	
		
			
				|  |  | +} base_call_data;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Additional call data used only for the server filter.
 | 
	
		
			
				|  |  | +typedef struct server_call_data {
 | 
	
		
			
				|  |  | +  base_call_data base;  // Must be first.
 | 
	
		
			
				|  |  | +  // The closure for receiving initial metadata.
 | 
	
		
			
				|  |  | +  grpc_closure recv_initial_metadata_ready;
 | 
	
		
			
				|  |  | +  // Received initial metadata batch.
 | 
	
		
			
				|  |  | +  grpc_metadata_batch* recv_initial_metadata;
 | 
	
		
			
				|  |  | +  // The original recv_initial_metadata_ready closure, which we chain to
 | 
	
		
			
				|  |  | +  // after our own closure is invoked.
 | 
	
		
			
				|  |  | +  grpc_closure* next_recv_initial_metadata_ready;
 | 
	
		
			
				|  |  | +} server_call_data;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Constructor for call_data.  Used for both client and server filters.
 | 
	
		
			
				|  |  | +static grpc_error* init_call_elem(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                  grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                                  grpc_call_element_args* args) {
 | 
	
		
			
				|  |  | +  // Note: size of call data is different between client and server.
 | 
	
		
			
				|  |  | +  memset(elem->call_data, 0, elem->filter->sizeof_call_data);
 | 
	
		
			
				|  |  | +  grpc_deadline_state_init(exec_ctx, elem, args);
 | 
	
		
			
				|  |  | +  return GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Destructor for call_data.  Used for both client and server filters.
 | 
	
		
			
				|  |  | +static void destroy_call_elem(grpc_exec_ctx* exec_ctx, grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                              const grpc_call_final_info* final_info,
 | 
	
		
			
				|  |  | +                              void* and_free_memory) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state_destroy(exec_ctx, elem);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Method for starting a call op for client filter.
 | 
	
		
			
				|  |  | +static void client_start_transport_stream_op(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                             grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                                             grpc_transport_stream_op* op) {
 | 
	
		
			
				|  |  | +  grpc_deadline_state_client_start_transport_stream_op(exec_ctx, elem, op);
 | 
	
		
			
				|  |  | +  // Chain to next filter.
 | 
	
		
			
				|  |  | +  grpc_call_next_op(exec_ctx, elem, op);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Callback for receiving initial metadata on the server.
 | 
	
		
			
				|  |  | +static void recv_initial_metadata_ready(grpc_exec_ctx* exec_ctx, void* arg,
 | 
	
		
			
				|  |  | +                                        grpc_error* error) {
 | 
	
		
			
				|  |  | +  grpc_call_element* elem = arg;
 | 
	
		
			
				|  |  | +  server_call_data* calld = elem->call_data;
 | 
	
		
			
				|  |  | +  // Get deadline from metadata and start the timer if needed.
 | 
	
		
			
				|  |  | +  start_timer_if_needed(exec_ctx, elem, calld->recv_initial_metadata->deadline);
 | 
	
		
			
				|  |  | +  // Invoke the next callback.
 | 
	
		
			
				|  |  | +  calld->next_recv_initial_metadata_ready->cb(
 | 
	
		
			
				|  |  | +      exec_ctx, calld->next_recv_initial_metadata_ready->cb_arg, error);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Method for starting a call op for server filter.
 | 
	
		
			
				|  |  | +static void server_start_transport_stream_op(grpc_exec_ctx* exec_ctx,
 | 
	
		
			
				|  |  | +                                             grpc_call_element* elem,
 | 
	
		
			
				|  |  | +                                             grpc_transport_stream_op* op) {
 | 
	
		
			
				|  |  | +  server_call_data* calld = elem->call_data;
 | 
	
		
			
				|  |  | +  if (op->cancel_error != GRPC_ERROR_NONE ||
 | 
	
		
			
				|  |  | +      op->close_error != GRPC_ERROR_NONE) {
 | 
	
		
			
				|  |  | +    cancel_timer_if_needed(exec_ctx, &calld->base.deadline_state);
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    // If we're receiving initial metadata, we need to get the deadline
 | 
	
		
			
				|  |  | +    // from the recv_initial_metadata_ready callback.  So we inject our
 | 
	
		
			
				|  |  | +    // own callback into that hook.
 | 
	
		
			
				|  |  | +    if (op->recv_initial_metadata_ready != NULL) {
 | 
	
		
			
				|  |  | +      calld->next_recv_initial_metadata_ready = op->recv_initial_metadata_ready;
 | 
	
		
			
				|  |  | +      calld->recv_initial_metadata = op->recv_initial_metadata;
 | 
	
		
			
				|  |  | +      grpc_closure_init(&calld->recv_initial_metadata_ready,
 | 
	
		
			
				|  |  | +                        recv_initial_metadata_ready, elem);
 | 
	
		
			
				|  |  | +      op->recv_initial_metadata_ready = &calld->recv_initial_metadata_ready;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // Make sure we know when the call is complete, so that we can cancel
 | 
	
		
			
				|  |  | +    // the timer.
 | 
	
		
			
				|  |  | +    // Note that we trigger this on recv_trailing_metadata, even though
 | 
	
		
			
				|  |  | +    // the client never sends trailing metadata, because this is the
 | 
	
		
			
				|  |  | +    // hook that tells us when the call is complete on the server side.
 | 
	
		
			
				|  |  | +    if (op->recv_trailing_metadata != NULL) {
 | 
	
		
			
				|  |  | +      inject_on_complete_cb(&calld->base.deadline_state, op);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  // Chain to next filter.
 | 
	
		
			
				|  |  | +  grpc_call_next_op(exec_ctx, elem, op);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const grpc_channel_filter grpc_client_deadline_filter = {
 | 
	
		
			
				|  |  | +    client_start_transport_stream_op,
 | 
	
		
			
				|  |  | +    grpc_channel_next_op,
 | 
	
		
			
				|  |  | +    sizeof(base_call_data),
 | 
	
		
			
				|  |  | +    init_call_elem,
 | 
	
		
			
				|  |  | +    grpc_call_stack_ignore_set_pollset_or_pollset_set,
 | 
	
		
			
				|  |  | +    destroy_call_elem,
 | 
	
		
			
				|  |  | +    0,  // sizeof(channel_data)
 | 
	
		
			
				|  |  | +    init_channel_elem,
 | 
	
		
			
				|  |  | +    destroy_channel_elem,
 | 
	
		
			
				|  |  | +    grpc_call_next_get_peer,
 | 
	
		
			
				|  |  | +    "deadline",
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const grpc_channel_filter grpc_server_deadline_filter = {
 | 
	
		
			
				|  |  | +    server_start_transport_stream_op,
 | 
	
		
			
				|  |  | +    grpc_channel_next_op,
 | 
	
		
			
				|  |  | +    sizeof(server_call_data),
 | 
	
		
			
				|  |  | +    init_call_elem,
 | 
	
		
			
				|  |  | +    grpc_call_stack_ignore_set_pollset_or_pollset_set,
 | 
	
		
			
				|  |  | +    destroy_call_elem,
 | 
	
		
			
				|  |  | +    0,  // sizeof(channel_data)
 | 
	
		
			
				|  |  | +    init_channel_elem,
 | 
	
		
			
				|  |  | +    destroy_channel_elem,
 | 
	
		
			
				|  |  | +    grpc_call_next_get_peer,
 | 
	
		
			
				|  |  | +    "deadline",
 | 
	
		
			
				|  |  | +};
 |