diff --git a/src/core/ext/client_channel/client_channel.c b/src/core/ext/client_channel/client_channel.c index e437340e143..7eb6c4a1ce9 100644 --- a/src/core/ext/client_channel/client_channel.c +++ b/src/core/ext/client_channel/client_channel.c @@ -740,7 +740,7 @@ static void apply_final_configuration_locked(grpc_exec_ctx *exec_ctx, grpc_slice_hash_table_unref(exec_ctx, method_params_table); } /* Start deadline timer. */ - grpc_deadline_state_start(exec_ctx, elem, calld->deadline); + grpc_deadline_state_reset(exec_ctx, elem, calld->deadline); } static void subchannel_ready_locked(grpc_exec_ctx *exec_ctx, void *arg, @@ -1090,6 +1090,7 @@ static grpc_error *cc_init_call_elem(grpc_exec_ctx *exec_ctx, calld->call_start_time = args->start_time; calld->deadline = gpr_convert_clock_type(args->deadline, GPR_CLOCK_MONOTONIC); calld->owning_call = args->call_stack; + grpc_deadline_state_start(exec_ctx, elem, calld->deadline); return GRPC_ERROR_NONE; } diff --git a/src/core/lib/channel/deadline_filter.c b/src/core/lib/channel/deadline_filter.c index 0e9443937cd..5a12d62f1d1 100644 --- a/src/core/lib/channel/deadline_filter.c +++ b/src/core/lib/channel/deadline_filter.c @@ -78,9 +78,21 @@ retry: (grpc_deadline_timer_state)gpr_atm_acq_load(&deadline_state->timer_state); switch (cur_state) { case GRPC_DEADLINE_STATE_PENDING: - case GRPC_DEADLINE_STATE_FINISHED: // Note: We do not start the timer if there is already a timer return; + case GRPC_DEADLINE_STATE_FINISHED: + if (gpr_atm_rel_cas(&deadline_state->timer_state, + GRPC_DEADLINE_STATE_FINISHED, + GRPC_DEADLINE_STATE_PENDING)) { + // If we've already created and destroyed a timer, we always create a + // new closure: we have no other guarantee that the inlined closure is + // not in use (it may hold a pending call to timer_callback) + closure = grpc_closure_create(timer_callback, elem, + grpc_schedule_on_exec_ctx); + } else { + goto retry; + } + break; case GRPC_DEADLINE_STATE_INITIAL: if (gpr_atm_rel_cas(&deadline_state->timer_state, GRPC_DEADLINE_STATE_INITIAL, @@ -177,6 +189,13 @@ void grpc_deadline_state_start(grpc_exec_ctx* exec_ctx, grpc_call_element* elem, } } +void grpc_deadline_state_reset(grpc_exec_ctx* exec_ctx, grpc_call_element* elem, + gpr_timespec new_deadline) { + grpc_deadline_state* deadline_state = elem->call_data; + cancel_timer_if_needed(exec_ctx, deadline_state); + start_timer_if_needed(exec_ctx, elem, new_deadline); +} + void grpc_deadline_state_client_start_transport_stream_op( grpc_exec_ctx* exec_ctx, grpc_call_element* elem, grpc_transport_stream_op* op) { diff --git a/src/core/lib/channel/deadline_filter.h b/src/core/lib/channel/deadline_filter.h index 363ac58b6d5..72cd5cb9296 100644 --- a/src/core/lib/channel/deadline_filter.h +++ b/src/core/lib/channel/deadline_filter.h @@ -73,6 +73,16 @@ void grpc_deadline_state_destroy(grpc_exec_ctx* exec_ctx, void grpc_deadline_state_start(grpc_exec_ctx* exec_ctx, grpc_call_element* elem, gpr_timespec deadline); +// Cancels the existing timer and starts a new one with new_deadline. +// +// Note: It is generally safe to call this with an earlier deadline +// value than the current one, but not the reverse. No checks are done +// to ensure that the timer callback is not invoked while it is in the +// process of being reset, which means that attempting to increase the +// deadline may result in the timer being called twice. +void grpc_deadline_state_reset(grpc_exec_ctx* exec_ctx, grpc_call_element* elem, + gpr_timespec new_deadline); + // To be called from the client-side filter's start_transport_stream_op() // method. Ensures that the deadline timer is cancelled when the call // is completed.