|  | @@ -174,343 +174,451 @@ static bool is_default_initial_metadata(grpc_metadata_batch *initial_metadata) {
 | 
	
		
			
				|  |  |    return initial_metadata->list.default_count == initial_metadata->list.count;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -grpc_chttp2_begin_write_result grpc_chttp2_begin_write(
 | 
	
		
			
				|  |  | -    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) {
 | 
	
		
			
				|  |  | -  grpc_chttp2_stream *s;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  /* stats histogram counters: we increment these throughout this function,
 | 
	
		
			
				|  |  | -     and at the end publish to the central stats histograms */
 | 
	
		
			
				|  |  | -  int flow_control_writes = 0;
 | 
	
		
			
				|  |  | -  int initial_metadata_writes = 0;
 | 
	
		
			
				|  |  | -  int trailing_metadata_writes = 0;
 | 
	
		
			
				|  |  | -  int message_writes = 0;
 | 
	
		
			
				|  |  | +namespace {
 | 
	
		
			
				|  |  | +class StreamWriteContext;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class WriteContext {
 | 
	
		
			
				|  |  | + public:
 | 
	
		
			
				|  |  | +  WriteContext(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) : t_(t) {
 | 
	
		
			
				|  |  | +    GRPC_STATS_INC_HTTP2_WRITES_BEGUN(exec_ctx);
 | 
	
		
			
				|  |  | +    GPR_TIMER_BEGIN("grpc_chttp2_begin_write", 0);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  GRPC_STATS_INC_HTTP2_WRITES_BEGUN(exec_ctx);
 | 
	
		
			
				|  |  | +  // TODO(ctiller): make this the destructor
 | 
	
		
			
				|  |  | +  void FlushStats(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    GRPC_STATS_INC_HTTP2_SEND_INITIAL_METADATA_PER_WRITE(
 | 
	
		
			
				|  |  | +        exec_ctx, initial_metadata_writes_);
 | 
	
		
			
				|  |  | +    GRPC_STATS_INC_HTTP2_SEND_MESSAGE_PER_WRITE(exec_ctx, message_writes_);
 | 
	
		
			
				|  |  | +    GRPC_STATS_INC_HTTP2_SEND_TRAILING_METADATA_PER_WRITE(
 | 
	
		
			
				|  |  | +        exec_ctx, trailing_metadata_writes_);
 | 
	
		
			
				|  |  | +    GRPC_STATS_INC_HTTP2_SEND_FLOWCTL_PER_WRITE(exec_ctx, flow_control_writes_);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  GPR_TIMER_BEGIN("grpc_chttp2_begin_write", 0);
 | 
	
		
			
				|  |  | +  void FlushSettings(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    if (t_->dirtied_local_settings && !t_->sent_local_settings) {
 | 
	
		
			
				|  |  | +      grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | +          &t_->outbuf, grpc_chttp2_settings_create(
 | 
	
		
			
				|  |  | +                           t_->settings[GRPC_SENT_SETTINGS],
 | 
	
		
			
				|  |  | +                           t_->settings[GRPC_LOCAL_SETTINGS],
 | 
	
		
			
				|  |  | +                           t_->force_send_settings, GRPC_CHTTP2_NUM_SETTINGS));
 | 
	
		
			
				|  |  | +      t_->force_send_settings = false;
 | 
	
		
			
				|  |  | +      t_->dirtied_local_settings = false;
 | 
	
		
			
				|  |  | +      t_->sent_local_settings = true;
 | 
	
		
			
				|  |  | +      GRPC_STATS_INC_HTTP2_SETTINGS_WRITES(exec_ctx);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  if (t->dirtied_local_settings && !t->sent_local_settings) {
 | 
	
		
			
				|  |  | -    grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | -        &t->outbuf,
 | 
	
		
			
				|  |  | -        grpc_chttp2_settings_create(
 | 
	
		
			
				|  |  | -            t->settings[GRPC_SENT_SETTINGS], t->settings[GRPC_LOCAL_SETTINGS],
 | 
	
		
			
				|  |  | -            t->force_send_settings, GRPC_CHTTP2_NUM_SETTINGS));
 | 
	
		
			
				|  |  | -    t->force_send_settings = 0;
 | 
	
		
			
				|  |  | -    t->dirtied_local_settings = 0;
 | 
	
		
			
				|  |  | -    t->sent_local_settings = 1;
 | 
	
		
			
				|  |  | -    GRPC_STATS_INC_HTTP2_SETTINGS_WRITES(exec_ctx);
 | 
	
		
			
				|  |  | +  void FlushQueuedBuffers(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    /* simple writes are queued to qbuf, and flushed here */
 | 
	
		
			
				|  |  | +    grpc_slice_buffer_move_into(&t_->qbuf, &t_->outbuf);
 | 
	
		
			
				|  |  | +    GPR_ASSERT(t_->qbuf.count == 0);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  for (size_t i = 0; i < t->ping_ack_count; i++) {
 | 
	
		
			
				|  |  | -    grpc_slice_buffer_add(&t->outbuf,
 | 
	
		
			
				|  |  | -                          grpc_chttp2_ping_create(1, t->ping_acks[i]));
 | 
	
		
			
				|  |  | +  void FlushWindowUpdates(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    uint32_t transport_announce =
 | 
	
		
			
				|  |  | +        grpc_chttp2_flowctl_maybe_send_transport_update(&t_->flow_control,
 | 
	
		
			
				|  |  | +                                                        t_->outbuf.count > 0);
 | 
	
		
			
				|  |  | +    if (transport_announce) {
 | 
	
		
			
				|  |  | +      grpc_transport_one_way_stats throwaway_stats;
 | 
	
		
			
				|  |  | +      grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | +          &t_->outbuf, grpc_chttp2_window_update_create(0, transport_announce,
 | 
	
		
			
				|  |  | +                                                        &throwaway_stats));
 | 
	
		
			
				|  |  | +      ResetPingRecvClock();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  t->ping_ack_count = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  /* simple writes are queued to qbuf, and flushed here */
 | 
	
		
			
				|  |  | -  grpc_slice_buffer_move_into(&t->qbuf, &t->outbuf);
 | 
	
		
			
				|  |  | -  GPR_ASSERT(t->qbuf.count == 0);
 | 
	
		
			
				|  |  | +  void FlushPingAcks() {
 | 
	
		
			
				|  |  | +    for (size_t i = 0; i < t_->ping_ack_count; i++) {
 | 
	
		
			
				|  |  | +      grpc_slice_buffer_add(&t_->outbuf,
 | 
	
		
			
				|  |  | +                            grpc_chttp2_ping_create(true, t_->ping_acks[i]));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    t_->ping_ack_count = 0;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  grpc_chttp2_hpack_compressor_set_max_table_size(
 | 
	
		
			
				|  |  | -      &t->hpack_compressor,
 | 
	
		
			
				|  |  | -      t->settings[GRPC_PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
 | 
	
		
			
				|  |  | +  void EnactHpackSettings(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    grpc_chttp2_hpack_compressor_set_max_table_size(
 | 
	
		
			
				|  |  | +        &t_->hpack_compressor,
 | 
	
		
			
				|  |  | +        t_->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | +                    [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  if (t->flow_control.remote_window > 0) {
 | 
	
		
			
				|  |  | -    while (grpc_chttp2_list_pop_stalled_by_transport(t, &s)) {
 | 
	
		
			
				|  |  | -      if (!t->closed && grpc_chttp2_list_add_writable_stream(t, s)) {
 | 
	
		
			
				|  |  | -        stream_ref_if_not_destroyed(&s->refcount->refs);
 | 
	
		
			
				|  |  | +  void UpdateStreamsNoLongerStalled() {
 | 
	
		
			
				|  |  | +    grpc_chttp2_stream *s;
 | 
	
		
			
				|  |  | +    while (grpc_chttp2_list_pop_stalled_by_transport(t_, &s)) {
 | 
	
		
			
				|  |  | +      if (!t_->closed && grpc_chttp2_list_add_writable_stream(t_, s)) {
 | 
	
		
			
				|  |  | +        if (!stream_ref_if_not_destroyed(&s->refcount->refs)) {
 | 
	
		
			
				|  |  | +          grpc_chttp2_list_remove_writable_stream(t_, s);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  grpc_chttp2_begin_write_result result = {false, false, false};
 | 
	
		
			
				|  |  | +  grpc_chttp2_stream *NextStream() {
 | 
	
		
			
				|  |  | +    if (t_->outbuf.length > target_write_size(t_)) {
 | 
	
		
			
				|  |  | +      result_.partial = true;
 | 
	
		
			
				|  |  | +      return nullptr;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  /* for each grpc_chttp2_stream that's become writable, frame it's data
 | 
	
		
			
				|  |  | -     (according to available window sizes) and add to the output buffer */
 | 
	
		
			
				|  |  | -  while (true) {
 | 
	
		
			
				|  |  | -    if (t->outbuf.length > target_write_size(t)) {
 | 
	
		
			
				|  |  | -      result.partial = true;
 | 
	
		
			
				|  |  | -      break;
 | 
	
		
			
				|  |  | +    grpc_chttp2_stream *s;
 | 
	
		
			
				|  |  | +    if (!grpc_chttp2_list_pop_writable_stream(t_, &s)) {
 | 
	
		
			
				|  |  | +      return nullptr;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return s;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void ResetPingRecvClock() {
 | 
	
		
			
				|  |  | +    if (!t_->is_client) {
 | 
	
		
			
				|  |  | +      t_->ping_recv_state.last_ping_recv_time = GRPC_MILLIS_INF_PAST;
 | 
	
		
			
				|  |  | +      t_->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void IncInitialMetadataWrites() { ++initial_metadata_writes_; }
 | 
	
		
			
				|  |  | +  void IncWindowUpdateWrites() { ++flow_control_writes_; }
 | 
	
		
			
				|  |  | +  void IncMessageWrites() { ++message_writes_; }
 | 
	
		
			
				|  |  | +  void IncTrailingMetadataWrites() { ++trailing_metadata_writes_; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void NoteScheduledResults() { result_.early_results_scheduled = true; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  grpc_chttp2_transport *transport() const { return t_; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  grpc_chttp2_begin_write_result Result() {
 | 
	
		
			
				|  |  | +    result_.writing = t_->outbuf.count > 0;
 | 
	
		
			
				|  |  | +    return result_;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | + private:
 | 
	
		
			
				|  |  | +  grpc_chttp2_transport *const t_;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  /* stats histogram counters: we increment these throughout this function,
 | 
	
		
			
				|  |  | +     and at the end publish to the central stats histograms */
 | 
	
		
			
				|  |  | +  int flow_control_writes_ = 0;
 | 
	
		
			
				|  |  | +  int initial_metadata_writes_ = 0;
 | 
	
		
			
				|  |  | +  int trailing_metadata_writes_ = 0;
 | 
	
		
			
				|  |  | +  int message_writes_ = 0;
 | 
	
		
			
				|  |  | +  grpc_chttp2_begin_write_result result_ = {false, false, false};
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class DataSendContext {
 | 
	
		
			
				|  |  | + public:
 | 
	
		
			
				|  |  | +  DataSendContext(WriteContext *write_context, grpc_chttp2_transport *t,
 | 
	
		
			
				|  |  | +                  grpc_chttp2_stream *s)
 | 
	
		
			
				|  |  | +      : write_context_(write_context),
 | 
	
		
			
				|  |  | +        t_(t),
 | 
	
		
			
				|  |  | +        s_(s),
 | 
	
		
			
				|  |  | +        sending_bytes_before_(s_->sending_bytes) {}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  uint32_t stream_remote_window() const {
 | 
	
		
			
				|  |  | +    return (uint32_t)GPR_MAX(
 | 
	
		
			
				|  |  | +        0, s_->flow_control.remote_window_delta +
 | 
	
		
			
				|  |  | +               (int64_t)t_->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | +                                    [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  uint32_t max_outgoing() const {
 | 
	
		
			
				|  |  | +    return (uint32_t)GPR_MIN(
 | 
	
		
			
				|  |  | +        t_->settings[GRPC_PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
 | 
	
		
			
				|  |  | +        GPR_MIN(stream_remote_window(), t_->flow_control.remote_window));
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  bool AnyOutgoing() const { return max_outgoing() != 0; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void FlushCompressedBytes() {
 | 
	
		
			
				|  |  | +    uint32_t send_bytes =
 | 
	
		
			
				|  |  | +        (uint32_t)GPR_MIN(max_outgoing(), s_->compressed_data_buffer.length);
 | 
	
		
			
				|  |  | +    bool is_last_data_frame =
 | 
	
		
			
				|  |  | +        (send_bytes == s_->compressed_data_buffer.length &&
 | 
	
		
			
				|  |  | +         s_->flow_controlled_buffer.length == 0 &&
 | 
	
		
			
				|  |  | +         s_->fetching_send_message == NULL);
 | 
	
		
			
				|  |  | +    if (is_last_data_frame && s_->send_trailing_metadata != NULL &&
 | 
	
		
			
				|  |  | +        s_->stream_compression_ctx != NULL) {
 | 
	
		
			
				|  |  | +      if (!grpc_stream_compress(s_->stream_compression_ctx,
 | 
	
		
			
				|  |  | +                                &s_->flow_controlled_buffer,
 | 
	
		
			
				|  |  | +                                &s_->compressed_data_buffer, NULL, MAX_SIZE_T,
 | 
	
		
			
				|  |  | +                                GRPC_STREAM_COMPRESSION_FLUSH_FINISH)) {
 | 
	
		
			
				|  |  | +        gpr_log(GPR_ERROR, "Stream compression failed.");
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      grpc_stream_compression_context_destroy(s_->stream_compression_ctx);
 | 
	
		
			
				|  |  | +      s_->stream_compression_ctx = NULL;
 | 
	
		
			
				|  |  | +      /* After finish, bytes in s->compressed_data_buffer may be
 | 
	
		
			
				|  |  | +       * more than max_outgoing. Start another round of the current
 | 
	
		
			
				|  |  | +       * while loop so that send_bytes and is_last_data_frame are
 | 
	
		
			
				|  |  | +       * recalculated. */
 | 
	
		
			
				|  |  | +      return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    is_last_frame_ = is_last_data_frame && s_->send_trailing_metadata != NULL &&
 | 
	
		
			
				|  |  | +                     grpc_metadata_batch_is_empty(s_->send_trailing_metadata);
 | 
	
		
			
				|  |  | +    grpc_chttp2_encode_data(s_->id, &s_->compressed_data_buffer, send_bytes,
 | 
	
		
			
				|  |  | +                            is_last_frame_, &s_->stats.outgoing, &t_->outbuf);
 | 
	
		
			
				|  |  | +    grpc_chttp2_flowctl_sent_data(&t_->flow_control, &s_->flow_control,
 | 
	
		
			
				|  |  | +                                  send_bytes);
 | 
	
		
			
				|  |  | +    if (s_->compressed_data_buffer.length == 0) {
 | 
	
		
			
				|  |  | +      s_->sending_bytes += s_->uncompressed_data_size;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    if (!grpc_chttp2_list_pop_writable_stream(t, &s)) {
 | 
	
		
			
				|  |  | -      break;
 | 
	
		
			
				|  |  | +  void CompressMoreBytes() {
 | 
	
		
			
				|  |  | +    if (s_->stream_compression_ctx == NULL) {
 | 
	
		
			
				|  |  | +      s_->stream_compression_ctx =
 | 
	
		
			
				|  |  | +          grpc_stream_compression_context_create(s_->stream_compression_method);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    s_->uncompressed_data_size = s_->flow_controlled_buffer.length;
 | 
	
		
			
				|  |  | +    if (!grpc_stream_compress(s_->stream_compression_ctx,
 | 
	
		
			
				|  |  | +                              &s_->flow_controlled_buffer,
 | 
	
		
			
				|  |  | +                              &s_->compressed_data_buffer, NULL, MAX_SIZE_T,
 | 
	
		
			
				|  |  | +                              GRPC_STREAM_COMPRESSION_FLUSH_SYNC)) {
 | 
	
		
			
				|  |  | +      gpr_log(GPR_ERROR, "Stream compression failed.");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  bool is_last_frame() const { return is_last_frame_; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    bool sent_initial_metadata = s->sent_initial_metadata;
 | 
	
		
			
				|  |  | -    bool now_writing = false;
 | 
	
		
			
				|  |  | +  void CallCallbacks(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    if (update_list(exec_ctx, t_, s_,
 | 
	
		
			
				|  |  | +                    (int64_t)(s_->sending_bytes - sending_bytes_before_),
 | 
	
		
			
				|  |  | +                    &s_->on_flow_controlled_cbs,
 | 
	
		
			
				|  |  | +                    &s_->flow_controlled_bytes_flowed, GRPC_ERROR_NONE)) {
 | 
	
		
			
				|  |  | +      write_context_->NoteScheduledResults();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | + private:
 | 
	
		
			
				|  |  | +  WriteContext *write_context_;
 | 
	
		
			
				|  |  | +  grpc_chttp2_transport *t_;
 | 
	
		
			
				|  |  | +  grpc_chttp2_stream *s_;
 | 
	
		
			
				|  |  | +  const size_t sending_bytes_before_;
 | 
	
		
			
				|  |  | +  bool is_last_frame_ = false;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class StreamWriteContext {
 | 
	
		
			
				|  |  | + public:
 | 
	
		
			
				|  |  | +  StreamWriteContext(WriteContext *write_context, grpc_chttp2_stream *s)
 | 
	
		
			
				|  |  | +      : write_context_(write_context), t_(write_context->transport()), s_(s) {
 | 
	
		
			
				|  |  |      GRPC_CHTTP2_IF_TRACING(
 | 
	
		
			
				|  |  | -        gpr_log(GPR_DEBUG, "W:%p %s[%d] im-(sent,send)=(%d,%d) announce=%d", t,
 | 
	
		
			
				|  |  | -                t->is_client ? "CLIENT" : "SERVER", s->id,
 | 
	
		
			
				|  |  | -                sent_initial_metadata, s->send_initial_metadata != NULL,
 | 
	
		
			
				|  |  | +        gpr_log(GPR_DEBUG, "W:%p %s[%d] im-(sent,send)=(%d,%d) announce=%d", t_,
 | 
	
		
			
				|  |  | +                t_->is_client ? "CLIENT" : "SERVER", s->id,
 | 
	
		
			
				|  |  | +                s->sent_initial_metadata, s->send_initial_metadata != NULL,
 | 
	
		
			
				|  |  |                  (int)(s->flow_control.local_window_delta -
 | 
	
		
			
				|  |  |                        s->flow_control.announced_window_delta)));
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    grpc_mdelem *extra_headers_for_trailing_metadata[2];
 | 
	
		
			
				|  |  | -    size_t num_extra_headers_for_trailing_metadata = 0;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +  void FlushInitialMetadata(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  |      /* send initial metadata if it's available */
 | 
	
		
			
				|  |  | -    if (!sent_initial_metadata && s->send_initial_metadata != NULL) {
 | 
	
		
			
				|  |  | -      // We skip this on the server side if there is no custom initial
 | 
	
		
			
				|  |  | -      // metadata, there are no messages to send, and we are also sending
 | 
	
		
			
				|  |  | -      // trailing metadata.  This results in a Trailers-Only response,
 | 
	
		
			
				|  |  | -      // which is required for retries, as per:
 | 
	
		
			
				|  |  | -      // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid
 | 
	
		
			
				|  |  | -      if (t->is_client || s->fetching_send_message != NULL ||
 | 
	
		
			
				|  |  | -          s->flow_controlled_buffer.length != 0 ||
 | 
	
		
			
				|  |  | -          s->send_trailing_metadata == NULL ||
 | 
	
		
			
				|  |  | -          !is_default_initial_metadata(s->send_initial_metadata)) {
 | 
	
		
			
				|  |  | -        grpc_encode_header_options hopt = {
 | 
	
		
			
				|  |  | -            s->id,  // stream_id
 | 
	
		
			
				|  |  | -            false,  // is_eof
 | 
	
		
			
				|  |  | -            t->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | -                       [GRPC_CHTTP2_SETTINGS_GRPC_ALLOW_TRUE_BINARY_METADATA] !=
 | 
	
		
			
				|  |  | -                0,  // use_true_binary_metadata
 | 
	
		
			
				|  |  | -            t->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | -                       [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],  // max_frame_size
 | 
	
		
			
				|  |  | -            &s->stats.outgoing                                 // stats
 | 
	
		
			
				|  |  | -        };
 | 
	
		
			
				|  |  | -        grpc_chttp2_encode_header(exec_ctx, &t->hpack_compressor, NULL, 0,
 | 
	
		
			
				|  |  | -                                  s->send_initial_metadata, &hopt, &t->outbuf);
 | 
	
		
			
				|  |  | -        now_writing = true;
 | 
	
		
			
				|  |  | -        if (!t->is_client) {
 | 
	
		
			
				|  |  | -          t->ping_recv_state.last_ping_recv_time = GRPC_MILLIS_INF_PAST;
 | 
	
		
			
				|  |  | -          t->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        initial_metadata_writes++;
 | 
	
		
			
				|  |  | -      } else {
 | 
	
		
			
				|  |  | -        GRPC_CHTTP2_IF_TRACING(
 | 
	
		
			
				|  |  | -            gpr_log(GPR_INFO, "not sending initial_metadata (Trailers-Only)"));
 | 
	
		
			
				|  |  | -        // When sending Trailers-Only, we need to move the :status and
 | 
	
		
			
				|  |  | -        // content-type headers to the trailers.
 | 
	
		
			
				|  |  | -        if (s->send_initial_metadata->idx.named.status != NULL) {
 | 
	
		
			
				|  |  | -          extra_headers_for_trailing_metadata
 | 
	
		
			
				|  |  | -              [num_extra_headers_for_trailing_metadata++] =
 | 
	
		
			
				|  |  | -                  &s->send_initial_metadata->idx.named.status->md;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        if (s->send_initial_metadata->idx.named.content_type != NULL) {
 | 
	
		
			
				|  |  | -          extra_headers_for_trailing_metadata
 | 
	
		
			
				|  |  | -              [num_extra_headers_for_trailing_metadata++] =
 | 
	
		
			
				|  |  | -                  &s->send_initial_metadata->idx.named.content_type->md;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        trailing_metadata_writes++;
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      s->send_initial_metadata = NULL;
 | 
	
		
			
				|  |  | -      s->sent_initial_metadata = true;
 | 
	
		
			
				|  |  | -      sent_initial_metadata = true;
 | 
	
		
			
				|  |  | -      result.early_results_scheduled = true;
 | 
	
		
			
				|  |  | -      grpc_chttp2_complete_closure_step(
 | 
	
		
			
				|  |  | -          exec_ctx, t, s, &s->send_initial_metadata_finished, GRPC_ERROR_NONE,
 | 
	
		
			
				|  |  | -          "send_initial_metadata_finished");
 | 
	
		
			
				|  |  | +    if (s_->sent_initial_metadata) return;
 | 
	
		
			
				|  |  | +    if (s_->send_initial_metadata == nullptr) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // We skip this on the server side if there is no custom initial
 | 
	
		
			
				|  |  | +    // metadata, there are no messages to send, and we are also sending
 | 
	
		
			
				|  |  | +    // trailing metadata.  This results in a Trailers-Only response,
 | 
	
		
			
				|  |  | +    // which is required for retries, as per:
 | 
	
		
			
				|  |  | +    // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid
 | 
	
		
			
				|  |  | +    if (!t_->is_client && s_->fetching_send_message == nullptr &&
 | 
	
		
			
				|  |  | +        s_->flow_controlled_buffer.length == 0 &&
 | 
	
		
			
				|  |  | +        s_->compressed_data_buffer.length == 0 &&
 | 
	
		
			
				|  |  | +        s_->send_trailing_metadata != nullptr &&
 | 
	
		
			
				|  |  | +        is_default_initial_metadata(s_->send_initial_metadata)) {
 | 
	
		
			
				|  |  | +      ConvertInitialMetadataToTrailingMetadata();
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      grpc_encode_header_options hopt = {
 | 
	
		
			
				|  |  | +          s_->id,  // stream_id
 | 
	
		
			
				|  |  | +          false,   // is_eof
 | 
	
		
			
				|  |  | +          t_->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | +                      [GRPC_CHTTP2_SETTINGS_GRPC_ALLOW_TRUE_BINARY_METADATA] !=
 | 
	
		
			
				|  |  | +              0,  // use_true_binary_metadata
 | 
	
		
			
				|  |  | +          t_->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | +                      [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],  // max_frame_size
 | 
	
		
			
				|  |  | +          &s_->stats.outgoing                                 // stats
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +      grpc_chttp2_encode_header(exec_ctx, &t_->hpack_compressor, NULL, 0,
 | 
	
		
			
				|  |  | +                                s_->send_initial_metadata, &hopt, &t_->outbuf);
 | 
	
		
			
				|  |  | +      write_context_->ResetPingRecvClock();
 | 
	
		
			
				|  |  | +      write_context_->IncInitialMetadataWrites();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    s_->send_initial_metadata = NULL;
 | 
	
		
			
				|  |  | +    s_->sent_initial_metadata = true;
 | 
	
		
			
				|  |  | +    write_context_->NoteScheduledResults();
 | 
	
		
			
				|  |  | +    grpc_chttp2_complete_closure_step(
 | 
	
		
			
				|  |  | +        exec_ctx, t_, s_, &s_->send_initial_metadata_finished, GRPC_ERROR_NONE,
 | 
	
		
			
				|  |  | +        "send_initial_metadata_finished");
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void FlushWindowUpdates(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  |      /* send any window updates */
 | 
	
		
			
				|  |  |      uint32_t stream_announce = grpc_chttp2_flowctl_maybe_send_stream_update(
 | 
	
		
			
				|  |  | -        &t->flow_control, &s->flow_control);
 | 
	
		
			
				|  |  | -    if (stream_announce > 0) {
 | 
	
		
			
				|  |  | -      grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | -          &t->outbuf, grpc_chttp2_window_update_create(s->id, stream_announce,
 | 
	
		
			
				|  |  | -                                                       &s->stats.outgoing));
 | 
	
		
			
				|  |  | -      if (!t->is_client) {
 | 
	
		
			
				|  |  | -        t->ping_recv_state.last_ping_recv_time = GRPC_MILLIS_INF_PAST;
 | 
	
		
			
				|  |  | -        t->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | +        &t_->flow_control, &s_->flow_control);
 | 
	
		
			
				|  |  | +    if (stream_announce == 0) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | +        &t_->outbuf, grpc_chttp2_window_update_create(s_->id, stream_announce,
 | 
	
		
			
				|  |  | +                                                      &s_->stats.outgoing));
 | 
	
		
			
				|  |  | +    write_context_->ResetPingRecvClock();
 | 
	
		
			
				|  |  | +    write_context_->IncWindowUpdateWrites();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void FlushData(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    if (!s_->sent_initial_metadata) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (s_->flow_controlled_buffer.length == 0 &&
 | 
	
		
			
				|  |  | +        s_->compressed_data_buffer.length == 0) {
 | 
	
		
			
				|  |  | +      return;  // early out: nothing to do
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    DataSendContext data_send_context(write_context_, t_, s_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (!data_send_context.AnyOutgoing()) {
 | 
	
		
			
				|  |  | +      if (t_->flow_control.remote_window == 0) {
 | 
	
		
			
				|  |  | +        report_stall(t_, s_, "transport");
 | 
	
		
			
				|  |  | +        grpc_chttp2_list_add_stalled_by_transport(t_, s_);
 | 
	
		
			
				|  |  | +      } else if (data_send_context.stream_remote_window() == 0) {
 | 
	
		
			
				|  |  | +        report_stall(t_, s_, "stream");
 | 
	
		
			
				|  |  | +        grpc_chttp2_list_add_stalled_by_stream(t_, s_);
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -      flow_control_writes++;
 | 
	
		
			
				|  |  | +      return;  // early out: nothing to do
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    if (sent_initial_metadata) {
 | 
	
		
			
				|  |  | -      /* send any body bytes, if allowed by flow control */
 | 
	
		
			
				|  |  | -      if (s->flow_controlled_buffer.length > 0 ||
 | 
	
		
			
				|  |  | -          s->compressed_data_buffer.length > 0) {
 | 
	
		
			
				|  |  | -        uint32_t stream_remote_window = (uint32_t)GPR_MAX(
 | 
	
		
			
				|  |  | -            0,
 | 
	
		
			
				|  |  | -            s->flow_control.remote_window_delta +
 | 
	
		
			
				|  |  | -                (int64_t)t->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | -                                    [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
 | 
	
		
			
				|  |  | -        uint32_t max_outgoing = (uint32_t)GPR_MIN(
 | 
	
		
			
				|  |  | -            t->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | -                       [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
 | 
	
		
			
				|  |  | -            GPR_MIN(stream_remote_window, t->flow_control.remote_window));
 | 
	
		
			
				|  |  | -        if (max_outgoing > 0) {
 | 
	
		
			
				|  |  | -          bool is_last_data_frame = false;
 | 
	
		
			
				|  |  | -          bool is_last_frame = false;
 | 
	
		
			
				|  |  | -          size_t sending_bytes_before = s->sending_bytes;
 | 
	
		
			
				|  |  | -          while ((s->flow_controlled_buffer.length > 0 ||
 | 
	
		
			
				|  |  | -                  s->compressed_data_buffer.length > 0) &&
 | 
	
		
			
				|  |  | -                 max_outgoing > 0) {
 | 
	
		
			
				|  |  | -            if (s->compressed_data_buffer.length > 0) {
 | 
	
		
			
				|  |  | -              uint32_t send_bytes = (uint32_t)GPR_MIN(
 | 
	
		
			
				|  |  | -                  max_outgoing, s->compressed_data_buffer.length);
 | 
	
		
			
				|  |  | -              is_last_data_frame =
 | 
	
		
			
				|  |  | -                  (send_bytes == s->compressed_data_buffer.length &&
 | 
	
		
			
				|  |  | -                   s->flow_controlled_buffer.length == 0 &&
 | 
	
		
			
				|  |  | -                   s->fetching_send_message == NULL);
 | 
	
		
			
				|  |  | -              if (is_last_data_frame && s->send_trailing_metadata != NULL &&
 | 
	
		
			
				|  |  | -                  s->stream_compression_ctx != NULL) {
 | 
	
		
			
				|  |  | -                if (!grpc_stream_compress(
 | 
	
		
			
				|  |  | -                        s->stream_compression_ctx, &s->flow_controlled_buffer,
 | 
	
		
			
				|  |  | -                        &s->compressed_data_buffer, NULL, MAX_SIZE_T,
 | 
	
		
			
				|  |  | -                        GRPC_STREAM_COMPRESSION_FLUSH_FINISH)) {
 | 
	
		
			
				|  |  | -                  gpr_log(GPR_ERROR, "Stream compression failed.");
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                grpc_stream_compression_context_destroy(
 | 
	
		
			
				|  |  | -                    s->stream_compression_ctx);
 | 
	
		
			
				|  |  | -                s->stream_compression_ctx = NULL;
 | 
	
		
			
				|  |  | -                /* After finish, bytes in s->compressed_data_buffer may be
 | 
	
		
			
				|  |  | -                 * more than max_outgoing. Start another round of the current
 | 
	
		
			
				|  |  | -                 * while loop so that send_bytes and is_last_data_frame are
 | 
	
		
			
				|  |  | -                 * recalculated. */
 | 
	
		
			
				|  |  | -                continue;
 | 
	
		
			
				|  |  | -              }
 | 
	
		
			
				|  |  | -              is_last_frame =
 | 
	
		
			
				|  |  | -                  is_last_data_frame && s->send_trailing_metadata != NULL &&
 | 
	
		
			
				|  |  | -                  grpc_metadata_batch_is_empty(s->send_trailing_metadata);
 | 
	
		
			
				|  |  | -              grpc_chttp2_encode_data(s->id, &s->compressed_data_buffer,
 | 
	
		
			
				|  |  | -                                      send_bytes, is_last_frame,
 | 
	
		
			
				|  |  | -                                      &s->stats.outgoing, &t->outbuf);
 | 
	
		
			
				|  |  | -              grpc_chttp2_flowctl_sent_data(&t->flow_control, &s->flow_control,
 | 
	
		
			
				|  |  | -                                            send_bytes);
 | 
	
		
			
				|  |  | -              max_outgoing -= send_bytes;
 | 
	
		
			
				|  |  | -              if (s->compressed_data_buffer.length == 0) {
 | 
	
		
			
				|  |  | -                s->sending_bytes += s->uncompressed_data_size;
 | 
	
		
			
				|  |  | -              }
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -              if (s->stream_compression_ctx == NULL) {
 | 
	
		
			
				|  |  | -                s->stream_compression_ctx =
 | 
	
		
			
				|  |  | -                    grpc_stream_compression_context_create(
 | 
	
		
			
				|  |  | -                        s->stream_compression_method);
 | 
	
		
			
				|  |  | -              }
 | 
	
		
			
				|  |  | -              s->uncompressed_data_size = s->flow_controlled_buffer.length;
 | 
	
		
			
				|  |  | -              if (!grpc_stream_compress(
 | 
	
		
			
				|  |  | -                      s->stream_compression_ctx, &s->flow_controlled_buffer,
 | 
	
		
			
				|  |  | -                      &s->compressed_data_buffer, NULL, MAX_SIZE_T,
 | 
	
		
			
				|  |  | -                      GRPC_STREAM_COMPRESSION_FLUSH_SYNC)) {
 | 
	
		
			
				|  |  | -                gpr_log(GPR_ERROR, "Stream compression failed.");
 | 
	
		
			
				|  |  | -              }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -          }
 | 
	
		
			
				|  |  | -          if (!t->is_client) {
 | 
	
		
			
				|  |  | -            t->ping_recv_state.last_ping_recv_time = 0;
 | 
	
		
			
				|  |  | -            t->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | -          }
 | 
	
		
			
				|  |  | -          if (is_last_frame) {
 | 
	
		
			
				|  |  | -            s->send_trailing_metadata = NULL;
 | 
	
		
			
				|  |  | -            s->sent_trailing_metadata = true;
 | 
	
		
			
				|  |  | -            if (!t->is_client && !s->read_closed) {
 | 
	
		
			
				|  |  | -              grpc_slice_buffer_add(&t->outbuf, grpc_chttp2_rst_stream_create(
 | 
	
		
			
				|  |  | -                                                    s->id, GRPC_HTTP2_NO_ERROR,
 | 
	
		
			
				|  |  | -                                                    &s->stats.outgoing));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            grpc_chttp2_mark_stream_closed(exec_ctx, t, s, !t->is_client, 1,
 | 
	
		
			
				|  |  | -                                           GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | -          }
 | 
	
		
			
				|  |  | -          result.early_results_scheduled |=
 | 
	
		
			
				|  |  | -              update_list(exec_ctx, t, s,
 | 
	
		
			
				|  |  | -                          (int64_t)(s->sending_bytes - sending_bytes_before),
 | 
	
		
			
				|  |  | -                          &s->on_flow_controlled_cbs,
 | 
	
		
			
				|  |  | -                          &s->flow_controlled_bytes_flowed, GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | -          now_writing = true;
 | 
	
		
			
				|  |  | -          if (s->flow_controlled_buffer.length > 0 ||
 | 
	
		
			
				|  |  | -              s->compressed_data_buffer.length > 0) {
 | 
	
		
			
				|  |  | -            GRPC_CHTTP2_STREAM_REF(s, "chttp2_writing:fork");
 | 
	
		
			
				|  |  | -            grpc_chttp2_list_add_writable_stream(t, s);
 | 
	
		
			
				|  |  | -          }
 | 
	
		
			
				|  |  | -          message_writes++;
 | 
	
		
			
				|  |  | -        } else if (t->flow_control.remote_window == 0) {
 | 
	
		
			
				|  |  | -          report_stall(t, s, "transport");
 | 
	
		
			
				|  |  | -          grpc_chttp2_list_add_stalled_by_transport(t, s);
 | 
	
		
			
				|  |  | -          now_writing = true;
 | 
	
		
			
				|  |  | -        } else if (stream_remote_window == 0) {
 | 
	
		
			
				|  |  | -          report_stall(t, s, "stream");
 | 
	
		
			
				|  |  | -          grpc_chttp2_list_add_stalled_by_stream(t, s);
 | 
	
		
			
				|  |  | -          now_writing = true;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    while ((s_->flow_controlled_buffer.length > 0 ||
 | 
	
		
			
				|  |  | +            s_->compressed_data_buffer.length > 0) &&
 | 
	
		
			
				|  |  | +           data_send_context.max_outgoing() > 0) {
 | 
	
		
			
				|  |  | +      if (s_->compressed_data_buffer.length > 0) {
 | 
	
		
			
				|  |  | +        data_send_context.FlushCompressedBytes();
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        data_send_context.CompressMoreBytes();
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -      if (s->send_trailing_metadata != NULL &&
 | 
	
		
			
				|  |  | -          s->fetching_send_message == NULL &&
 | 
	
		
			
				|  |  | -          s->flow_controlled_buffer.length == 0 &&
 | 
	
		
			
				|  |  | -          s->compressed_data_buffer.length == 0) {
 | 
	
		
			
				|  |  | -        GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "sending trailing_metadata"));
 | 
	
		
			
				|  |  | -        if (grpc_metadata_batch_is_empty(s->send_trailing_metadata)) {
 | 
	
		
			
				|  |  | -          grpc_chttp2_encode_data(s->id, &s->flow_controlled_buffer, 0, true,
 | 
	
		
			
				|  |  | -                                  &s->stats.outgoing, &t->outbuf);
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -          grpc_encode_header_options hopt = {
 | 
	
		
			
				|  |  | -              s->id, true,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -              t->settings
 | 
	
		
			
				|  |  | -                      [GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    write_context_->ResetPingRecvClock();
 | 
	
		
			
				|  |  | +    if (data_send_context.is_last_frame()) {
 | 
	
		
			
				|  |  | +      SentLastFrame(exec_ctx);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    data_send_context.CallCallbacks(exec_ctx);
 | 
	
		
			
				|  |  | +    stream_became_writable_ = true;
 | 
	
		
			
				|  |  | +    if (s_->flow_controlled_buffer.length > 0 ||
 | 
	
		
			
				|  |  | +        s_->compressed_data_buffer.length > 0) {
 | 
	
		
			
				|  |  | +      GRPC_CHTTP2_STREAM_REF(s_, "chttp2_writing:fork");
 | 
	
		
			
				|  |  | +      grpc_chttp2_list_add_writable_stream(t_, s_);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    write_context_->IncMessageWrites();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void FlushTrailingMetadata(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    if (!s_->sent_initial_metadata) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (s_->send_trailing_metadata == NULL) return;
 | 
	
		
			
				|  |  | +    if (s_->fetching_send_message != NULL) return;
 | 
	
		
			
				|  |  | +    if (s_->flow_controlled_buffer.length != 0) return;
 | 
	
		
			
				|  |  | +    if (s_->compressed_data_buffer.length != 0) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "sending trailing_metadata"));
 | 
	
		
			
				|  |  | +    if (grpc_metadata_batch_is_empty(s_->send_trailing_metadata)) {
 | 
	
		
			
				|  |  | +      grpc_chttp2_encode_data(s_->id, &s_->flow_controlled_buffer, 0, true,
 | 
	
		
			
				|  |  | +                              &s_->stats.outgoing, &t_->outbuf);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      grpc_encode_header_options hopt = {
 | 
	
		
			
				|  |  | +          s_->id, true,
 | 
	
		
			
				|  |  | +          t_->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  |                        [GRPC_CHTTP2_SETTINGS_GRPC_ALLOW_TRUE_BINARY_METADATA] !=
 | 
	
		
			
				|  |  | -                  0,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -              t->settings[GRPC_PEER_SETTINGS]
 | 
	
		
			
				|  |  | -                         [GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
 | 
	
		
			
				|  |  | -              &s->stats.outgoing};
 | 
	
		
			
				|  |  | -          grpc_chttp2_encode_header(exec_ctx, &t->hpack_compressor,
 | 
	
		
			
				|  |  | -                                    extra_headers_for_trailing_metadata,
 | 
	
		
			
				|  |  | -                                    num_extra_headers_for_trailing_metadata,
 | 
	
		
			
				|  |  | -                                    s->send_trailing_metadata, &hopt,
 | 
	
		
			
				|  |  | -                                    &t->outbuf);
 | 
	
		
			
				|  |  | -          trailing_metadata_writes++;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        s->send_trailing_metadata = NULL;
 | 
	
		
			
				|  |  | -        s->sent_trailing_metadata = true;
 | 
	
		
			
				|  |  | -        if (!t->is_client) {
 | 
	
		
			
				|  |  | -          t->ping_recv_state.last_ping_recv_time = GRPC_MILLIS_INF_PAST;
 | 
	
		
			
				|  |  | -          t->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        if (!t->is_client && !s->read_closed) {
 | 
	
		
			
				|  |  | -          grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | -              &t->outbuf, grpc_chttp2_rst_stream_create(
 | 
	
		
			
				|  |  | -                              s->id, GRPC_HTTP2_NO_ERROR, &s->stats.outgoing));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        grpc_chttp2_mark_stream_closed(exec_ctx, t, s, !t->is_client, 1,
 | 
	
		
			
				|  |  | -                                       GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | -        now_writing = true;
 | 
	
		
			
				|  |  | -        result.early_results_scheduled = true;
 | 
	
		
			
				|  |  | -        grpc_chttp2_complete_closure_step(
 | 
	
		
			
				|  |  | -            exec_ctx, t, s, &s->send_trailing_metadata_finished,
 | 
	
		
			
				|  |  | -            GRPC_ERROR_NONE, "send_trailing_metadata_finished");
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | +              0,
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          t_->settings[GRPC_PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE],
 | 
	
		
			
				|  |  | +          &s_->stats.outgoing};
 | 
	
		
			
				|  |  | +      grpc_chttp2_encode_header(exec_ctx, &t_->hpack_compressor,
 | 
	
		
			
				|  |  | +                                extra_headers_for_trailing_metadata_,
 | 
	
		
			
				|  |  | +                                num_extra_headers_for_trailing_metadata_,
 | 
	
		
			
				|  |  | +                                s_->send_trailing_metadata, &hopt, &t_->outbuf);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    write_context_->IncTrailingMetadataWrites();
 | 
	
		
			
				|  |  | +    write_context_->ResetPingRecvClock();
 | 
	
		
			
				|  |  | +    SentLastFrame(exec_ctx);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    write_context_->NoteScheduledResults();
 | 
	
		
			
				|  |  | +    grpc_chttp2_complete_closure_step(
 | 
	
		
			
				|  |  | +        exec_ctx, t_, s_, &s_->send_trailing_metadata_finished, GRPC_ERROR_NONE,
 | 
	
		
			
				|  |  | +        "send_trailing_metadata_finished");
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  bool stream_became_writable() { return stream_became_writable_; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | + private:
 | 
	
		
			
				|  |  | +  void ConvertInitialMetadataToTrailingMetadata() {
 | 
	
		
			
				|  |  | +    GRPC_CHTTP2_IF_TRACING(
 | 
	
		
			
				|  |  | +        gpr_log(GPR_INFO, "not sending initial_metadata (Trailers-Only)"));
 | 
	
		
			
				|  |  | +    // When sending Trailers-Only, we need to move the :status and
 | 
	
		
			
				|  |  | +    // content-type headers to the trailers.
 | 
	
		
			
				|  |  | +    if (s_->send_initial_metadata->idx.named.status != NULL) {
 | 
	
		
			
				|  |  | +      extra_headers_for_trailing_metadata_
 | 
	
		
			
				|  |  | +          [num_extra_headers_for_trailing_metadata_++] =
 | 
	
		
			
				|  |  | +              &s_->send_initial_metadata->idx.named.status->md;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    if (s_->send_initial_metadata->idx.named.content_type != NULL) {
 | 
	
		
			
				|  |  | +      extra_headers_for_trailing_metadata_
 | 
	
		
			
				|  |  | +          [num_extra_headers_for_trailing_metadata_++] =
 | 
	
		
			
				|  |  | +              &s_->send_initial_metadata->idx.named.content_type->md;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  void SentLastFrame(grpc_exec_ctx *exec_ctx) {
 | 
	
		
			
				|  |  | +    s_->send_trailing_metadata = NULL;
 | 
	
		
			
				|  |  | +    s_->sent_trailing_metadata = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (!t_->is_client && !s_->read_closed) {
 | 
	
		
			
				|  |  | +      grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | +          &t_->outbuf, grpc_chttp2_rst_stream_create(
 | 
	
		
			
				|  |  | +                           s_->id, GRPC_HTTP2_NO_ERROR, &s_->stats.outgoing));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    grpc_chttp2_mark_stream_closed(exec_ctx, t_, s_, !t_->is_client, true,
 | 
	
		
			
				|  |  | +                                   GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    if (now_writing) {
 | 
	
		
			
				|  |  | -      GRPC_STATS_INC_HTTP2_SEND_INITIAL_METADATA_PER_WRITE(
 | 
	
		
			
				|  |  | -          exec_ctx, initial_metadata_writes);
 | 
	
		
			
				|  |  | -      GRPC_STATS_INC_HTTP2_SEND_MESSAGE_PER_WRITE(exec_ctx, message_writes);
 | 
	
		
			
				|  |  | -      GRPC_STATS_INC_HTTP2_SEND_TRAILING_METADATA_PER_WRITE(
 | 
	
		
			
				|  |  | -          exec_ctx, trailing_metadata_writes);
 | 
	
		
			
				|  |  | -      GRPC_STATS_INC_HTTP2_SEND_FLOWCTL_PER_WRITE(exec_ctx,
 | 
	
		
			
				|  |  | -                                                  flow_control_writes);
 | 
	
		
			
				|  |  | +  WriteContext *const write_context_;
 | 
	
		
			
				|  |  | +  grpc_chttp2_transport *const t_;
 | 
	
		
			
				|  |  | +  grpc_chttp2_stream *const s_;
 | 
	
		
			
				|  |  | +  bool stream_became_writable_ = false;
 | 
	
		
			
				|  |  | +  grpc_mdelem *extra_headers_for_trailing_metadata_[2];
 | 
	
		
			
				|  |  | +  size_t num_extra_headers_for_trailing_metadata_ = 0;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +}  // namespace
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +grpc_chttp2_begin_write_result grpc_chttp2_begin_write(
 | 
	
		
			
				|  |  | +    grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) {
 | 
	
		
			
				|  |  | +  WriteContext ctx(exec_ctx, t);
 | 
	
		
			
				|  |  | +  ctx.FlushSettings(exec_ctx);
 | 
	
		
			
				|  |  | +  ctx.FlushPingAcks();
 | 
	
		
			
				|  |  | +  ctx.FlushQueuedBuffers(exec_ctx);
 | 
	
		
			
				|  |  | +  ctx.EnactHpackSettings(exec_ctx);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (t->flow_control.remote_window > 0) {
 | 
	
		
			
				|  |  | +    ctx.UpdateStreamsNoLongerStalled();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  /* for each grpc_chttp2_stream that's become writable, frame it's data
 | 
	
		
			
				|  |  | +     (according to available window sizes) and add to the output buffer */
 | 
	
		
			
				|  |  | +  while (grpc_chttp2_stream *s = ctx.NextStream()) {
 | 
	
		
			
				|  |  | +    StreamWriteContext stream_ctx(&ctx, s);
 | 
	
		
			
				|  |  | +    stream_ctx.FlushInitialMetadata(exec_ctx);
 | 
	
		
			
				|  |  | +    stream_ctx.FlushWindowUpdates(exec_ctx);
 | 
	
		
			
				|  |  | +    stream_ctx.FlushData(exec_ctx);
 | 
	
		
			
				|  |  | +    stream_ctx.FlushTrailingMetadata(exec_ctx);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (stream_ctx.stream_became_writable()) {
 | 
	
		
			
				|  |  |        if (!grpc_chttp2_list_add_writing_stream(t, s)) {
 | 
	
		
			
				|  |  |          /* already in writing list: drop ref */
 | 
	
		
			
				|  |  |          GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:already_writing");
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        /* ref will be dropped at end of write */
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |        GRPC_CHTTP2_STREAM_UNREF(exec_ctx, s, "chttp2_writing:no_write");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  maybe_initiate_ping(exec_ctx, t);
 | 
	
		
			
				|  |  | +  ctx.FlushWindowUpdates(exec_ctx);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  uint32_t transport_announce = grpc_chttp2_flowctl_maybe_send_transport_update(
 | 
	
		
			
				|  |  | -      &t->flow_control, t->outbuf.count > 0);
 | 
	
		
			
				|  |  | -  if (transport_announce) {
 | 
	
		
			
				|  |  | -    grpc_transport_one_way_stats throwaway_stats;
 | 
	
		
			
				|  |  | -    grpc_slice_buffer_add(
 | 
	
		
			
				|  |  | -        &t->outbuf, grpc_chttp2_window_update_create(0, transport_announce,
 | 
	
		
			
				|  |  | -                                                     &throwaway_stats));
 | 
	
		
			
				|  |  | -    if (!t->is_client) {
 | 
	
		
			
				|  |  | -      t->ping_recv_state.last_ping_recv_time = GRPC_MILLIS_INF_PAST;
 | 
	
		
			
				|  |  | -      t->ping_recv_state.ping_strikes = 0;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +  maybe_initiate_ping(exec_ctx, t);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    GPR_TIMER_END("grpc_chttp2_begin_write", 0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  result.writing = t->outbuf.count > 0;
 | 
	
		
			
				|  |  | -  return result;
 | 
	
		
			
				|  |  | +  return ctx.Result();
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void grpc_chttp2_end_write(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t,
 |