|  | @@ -309,6 +309,7 @@ static void push_setting(transport *t, grpc_chttp2_setting_id id,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int prepare_callbacks(transport *t);
 | 
	
		
			
				|  |  |  static void run_callbacks(transport *t, const grpc_transport_callbacks *cb);
 | 
	
		
			
				|  |  | +static void call_cb_closed(transport *t, const grpc_transport_callbacks *cb);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int prepare_write(transport *t);
 | 
	
		
			
				|  |  |  static void perform_write(transport *t, grpc_endpoint *ep);
 | 
	
	
		
			
				|  | @@ -516,13 +517,29 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup,
 | 
	
		
			
				|  |  |  static void destroy_transport(grpc_transport *gt) {
 | 
	
		
			
				|  |  |    transport *t = (transport *)gt;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  gpr_mu_lock(&t->mu);
 | 
	
		
			
				|  |  | +  lock(t);
 | 
	
		
			
				|  |  |    t->destroying = 1;
 | 
	
		
			
				|  |  | -  while (t->calling_back) {
 | 
	
		
			
				|  |  | +  /* Wait for pending stuff to finish.
 | 
	
		
			
				|  |  | +     We need to be not calling back to ensure that closed() gets a chance to
 | 
	
		
			
				|  |  | +     trigger if needed during unlock() before we die.
 | 
	
		
			
				|  |  | +     We need to be not writing as cancellation finalization may produce some
 | 
	
		
			
				|  |  | +     callbacks that NEED to be made to close out some streams when t->writing
 | 
	
		
			
				|  |  | +     becomes 0. */
 | 
	
		
			
				|  |  | +  while (t->calling_back || t->writing) {
 | 
	
		
			
				|  |  |      gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  t->cb = NULL;
 | 
	
		
			
				|  |  | -  gpr_mu_unlock(&t->mu);
 | 
	
		
			
				|  |  | +  drop_connection(t);
 | 
	
		
			
				|  |  | +  unlock(t);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  /* The drop_connection() above puts the transport into an error state, and
 | 
	
		
			
				|  |  | +     the follow-up unlock should then (as part of the cleanup work it does)
 | 
	
		
			
				|  |  | +     ensure that cb is NULL, and therefore not call back anything further.
 | 
	
		
			
				|  |  | +     This check validates this very subtle behavior.
 | 
	
		
			
				|  |  | +     It's shutdown path, so I don't believe an extra lock pair is going to be
 | 
	
		
			
				|  |  | +     problematic for performance. */
 | 
	
		
			
				|  |  | +  lock(t);
 | 
	
		
			
				|  |  | +  GPR_ASSERT(!t->cb);
 | 
	
		
			
				|  |  | +  unlock(t);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    unref_transport(t);
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -680,6 +697,7 @@ static void stream_list_add_tail(transport *t, stream *s, stream_list_id id) {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void stream_list_join(transport *t, stream *s, stream_list_id id) {
 | 
	
		
			
				|  |  | +  if (id == PENDING_CALLBACKS) GPR_ASSERT(t->cb != NULL || t->error_state == ERROR_STATE_NONE);
 | 
	
		
			
				|  |  |    if (s->included[id]) {
 | 
	
		
			
				|  |  |      return;
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -738,7 +756,7 @@ static void unlock(transport *t) {
 | 
	
		
			
				|  |  |      if (perform_callbacks) {
 | 
	
		
			
				|  |  |        t->calling_back = 1;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    if (t->error_state == ERROR_STATE_SEEN) {
 | 
	
		
			
				|  |  | +    if (t->error_state == ERROR_STATE_SEEN && !t->writing) {
 | 
	
		
			
				|  |  |        call_closed = 1;
 | 
	
		
			
				|  |  |        t->calling_back = 1;
 | 
	
		
			
				|  |  |        t->cb = NULL;  /* no more callbacks */
 | 
	
	
		
			
				|  | @@ -772,7 +790,7 @@ static void unlock(transport *t) {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    if (call_closed) {
 | 
	
		
			
				|  |  | -    cb->closed(t->cb_user_data, &t->base);
 | 
	
		
			
				|  |  | +    call_cb_closed(t, cb);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    /* write some bytes if necessary */
 | 
	
	
		
			
				|  | @@ -903,13 +921,16 @@ static void finish_write_common(transport *t, int success) {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    while ((s = stream_list_remove_head(t, WRITTEN_CLOSED))) {
 | 
	
		
			
				|  |  |      s->sent_write_closed = 1;
 | 
	
		
			
				|  |  | -    stream_list_join(t, s, PENDING_CALLBACKS);
 | 
	
		
			
				|  |  | +    if (!s->cancelled) stream_list_join(t, s, PENDING_CALLBACKS);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    t->outbuf.count = 0;
 | 
	
		
			
				|  |  |    t->outbuf.length = 0;
 | 
	
		
			
				|  |  |    /* leave the writing flag up on shutdown to prevent further writes in unlock()
 | 
	
		
			
				|  |  |       from starting */
 | 
	
		
			
				|  |  |    t->writing = 0;
 | 
	
		
			
				|  |  | +  if (t->destroying) {
 | 
	
		
			
				|  |  | +    gpr_cv_signal(&t->cv);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |    if (!t->reading) {
 | 
	
		
			
				|  |  |      grpc_endpoint_destroy(t->ep);
 | 
	
		
			
				|  |  |      t->ep = NULL;
 | 
	
	
		
			
				|  | @@ -979,7 +1000,8 @@ static void send_batch(grpc_transport *gt, grpc_stream *gs, grpc_stream_op *ops,
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  |      grpc_sopb_append(&t->nuke_later_sopb, ops, ops_count);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (is_last && s->outgoing_sopb.nops == 0 && s->read_closed) {
 | 
	
		
			
				|  |  | +  if (is_last && s->outgoing_sopb.nops == 0 && s->read_closed &&
 | 
	
		
			
				|  |  | +      !s->published_close) {
 | 
	
		
			
				|  |  |      stream_list_join(t, s, PENDING_CALLBACKS);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1765,6 +1787,10 @@ static void run_callbacks(transport *t, const grpc_transport_callbacks *cb) {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +static void call_cb_closed(transport *t, const grpc_transport_callbacks *cb) {
 | 
	
		
			
				|  |  | +  cb->closed(t->cb_user_data, &t->base);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  static void add_to_pollset(grpc_transport *gt, grpc_pollset *pollset) {
 | 
	
		
			
				|  |  |    transport *t = (transport *)gt;
 | 
	
		
			
				|  |  |    lock(t);
 |