|  | @@ -101,6 +101,29 @@ class CallbackUnaryCallImpl {
 | 
	
		
			
				|  |  |      call.PerformOps(ops);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Base class for public API classes.
 | 
	
		
			
				|  |  | +class ClientReactor {
 | 
	
		
			
				|  |  | + public:
 | 
	
		
			
				|  |  | +  /// Called by the library when all operations associated with this RPC have
 | 
	
		
			
				|  |  | +  /// completed and all Holds have been removed. OnDone provides the RPC status
 | 
	
		
			
				|  |  | +  /// outcome for both successful and failed RPCs. If it is never called on an
 | 
	
		
			
				|  |  | +  /// RPC, it indicates an application-level problem (like failure to remove a
 | 
	
		
			
				|  |  | +  /// hold).
 | 
	
		
			
				|  |  | +  ///
 | 
	
		
			
				|  |  | +  /// \param[in] s The status outcome of this RPC
 | 
	
		
			
				|  |  | +  virtual void OnDone(const ::grpc::Status& /*s*/) = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  /// InternalScheduleOnDone is not part of the API and is not meant to be
 | 
	
		
			
				|  |  | +  /// overridden. It is virtual to allow successful builds for certain bazel
 | 
	
		
			
				|  |  | +  /// build users that only want to depend on gRPC codegen headers and not the
 | 
	
		
			
				|  |  | +  /// full library (although this is not a generally-supported option). Although
 | 
	
		
			
				|  |  | +  /// the virtual call is slower than a direct call, this function is
 | 
	
		
			
				|  |  | +  /// heavyweight and the cost of the virtual call is not much in comparison.
 | 
	
		
			
				|  |  | +  /// This function may be removed or devirtualized in the future.
 | 
	
		
			
				|  |  | +  virtual void InternalScheduleOnDone(::grpc::Status s);
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  }  // namespace internal
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // Forward declarations
 | 
	
	
		
			
				|  | @@ -189,7 +212,7 @@ class ClientCallbackUnary {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /// \a ClientBidiReactor is the interface for a bidirectional streaming RPC.
 | 
	
		
			
				|  |  |  template <class Request, class Response>
 | 
	
		
			
				|  |  | -class ClientBidiReactor {
 | 
	
		
			
				|  |  | +class ClientBidiReactor : public internal::ClientReactor {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  |    virtual ~ClientBidiReactor() {}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -282,7 +305,7 @@ class ClientBidiReactor {
 | 
	
		
			
				|  |  |    /// (like failure to remove a hold).
 | 
	
		
			
				|  |  |    ///
 | 
	
		
			
				|  |  |    /// \param[in] s The status outcome of this RPC
 | 
	
		
			
				|  |  | -  virtual void OnDone(const ::grpc::Status& /*s*/) {}
 | 
	
		
			
				|  |  | +  void OnDone(const ::grpc::Status& /*s*/) override {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    /// Notifies the application that a read of initial metadata from the
 | 
	
		
			
				|  |  |    /// server is done. If the application chooses not to implement this method,
 | 
	
	
		
			
				|  | @@ -327,7 +350,7 @@ class ClientBidiReactor {
 | 
	
		
			
				|  |  |  /// \a ClientReadReactor is the interface for a server-streaming RPC.
 | 
	
		
			
				|  |  |  /// All public methods behave as in ClientBidiReactor.
 | 
	
		
			
				|  |  |  template <class Response>
 | 
	
		
			
				|  |  | -class ClientReadReactor {
 | 
	
		
			
				|  |  | +class ClientReadReactor : public internal::ClientReactor {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  |    virtual ~ClientReadReactor() {}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -341,7 +364,7 @@ class ClientReadReactor {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    void RemoveHold() { reader_->RemoveHold(); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  virtual void OnDone(const ::grpc::Status& /*s*/) {}
 | 
	
		
			
				|  |  | +  void OnDone(const ::grpc::Status& /*s*/) override {}
 | 
	
		
			
				|  |  |    virtual void OnReadInitialMetadataDone(bool /*ok*/) {}
 | 
	
		
			
				|  |  |    virtual void OnReadDone(bool /*ok*/) {}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -354,7 +377,7 @@ class ClientReadReactor {
 | 
	
		
			
				|  |  |  /// \a ClientWriteReactor is the interface for a client-streaming RPC.
 | 
	
		
			
				|  |  |  /// All public methods behave as in ClientBidiReactor.
 | 
	
		
			
				|  |  |  template <class Request>
 | 
	
		
			
				|  |  | -class ClientWriteReactor {
 | 
	
		
			
				|  |  | +class ClientWriteReactor : public internal::ClientReactor {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  |    virtual ~ClientWriteReactor() {}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -377,7 +400,7 @@ class ClientWriteReactor {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    void RemoveHold() { writer_->RemoveHold(); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  virtual void OnDone(const ::grpc::Status& /*s*/) {}
 | 
	
		
			
				|  |  | +  void OnDone(const ::grpc::Status& /*s*/) override {}
 | 
	
		
			
				|  |  |    virtual void OnReadInitialMetadataDone(bool /*ok*/) {}
 | 
	
		
			
				|  |  |    virtual void OnWriteDone(bool /*ok*/) {}
 | 
	
		
			
				|  |  |    virtual void OnWritesDoneDone(bool /*ok*/) {}
 | 
	
	
		
			
				|  | @@ -385,6 +408,7 @@ class ClientWriteReactor {
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    friend class ClientCallbackWriter<Request>;
 | 
	
		
			
				|  |  |    void BindWriter(ClientCallbackWriter<Request>* writer) { writer_ = writer; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    ClientCallbackWriter<Request>* writer_;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -399,12 +423,12 @@ class ClientWriteReactor {
 | 
	
		
			
				|  |  |  /// call (that is part of the unary call itself) and there is no reactor object
 | 
	
		
			
				|  |  |  /// being created as a result of this call, we keep a consistent 2-phase
 | 
	
		
			
				|  |  |  /// initiation API among all the reactor flavors.
 | 
	
		
			
				|  |  | -class ClientUnaryReactor {
 | 
	
		
			
				|  |  | +class ClientUnaryReactor : public internal::ClientReactor {
 | 
	
		
			
				|  |  |   public:
 | 
	
		
			
				|  |  |    virtual ~ClientUnaryReactor() {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void StartCall() { call_->StartCall(); }
 | 
	
		
			
				|  |  | -  virtual void OnDone(const ::grpc::Status& /*s*/) {}
 | 
	
		
			
				|  |  | +  void OnDone(const ::grpc::Status& /*s*/) override {}
 | 
	
		
			
				|  |  |    virtual void OnReadInitialMetadataDone(bool /*ok*/) {}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
	
		
			
				|  | @@ -444,7 +468,13 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |    // there are no tests catching the compiler warning.
 | 
	
		
			
				|  |  |    static void operator delete(void*, void*) { GPR_CODEGEN_ASSERT(false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  void MaybeFinish() {
 | 
	
		
			
				|  |  | +  // MaybeFinish can be called from reactions or from user-initiated operations
 | 
	
		
			
				|  |  | +  // like StartCall or RemoveHold. If this is the last operation or hold on this
 | 
	
		
			
				|  |  | +  // object, it will invoke the OnDone reaction. If MaybeFinish was called from
 | 
	
		
			
				|  |  | +  // a reaction, it can call OnDone directly. If not, it would need to schedule
 | 
	
		
			
				|  |  | +  // OnDone onto an executor thread to avoid the possibility of deadlocking with
 | 
	
		
			
				|  |  | +  // any locks in the user code that invoked it.
 | 
	
		
			
				|  |  | +  void MaybeFinish(bool from_reaction) {
 | 
	
		
			
				|  |  |      if (GPR_UNLIKELY(callbacks_outstanding_.fetch_sub(
 | 
	
		
			
				|  |  |                           1, std::memory_order_acq_rel) == 1)) {
 | 
	
		
			
				|  |  |        ::grpc::Status s = std::move(finish_status_);
 | 
	
	
		
			
				|  | @@ -452,7 +482,11 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |        auto* call = call_.call();
 | 
	
		
			
				|  |  |        this->~ClientCallbackReaderWriterImpl();
 | 
	
		
			
				|  |  |        ::grpc::g_core_codegen_interface->grpc_call_unref(call);
 | 
	
		
			
				|  |  | -      reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(from_reaction)) {
 | 
	
		
			
				|  |  | +        reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        reactor->InternalScheduleOnDone(std::move(s));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -461,76 +495,51 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |      // 1. Send initial metadata (unless corked) + recv initial metadata
 | 
	
		
			
				|  |  |      // 2. Any read backlog
 | 
	
		
			
				|  |  |      // 3. Any write backlog
 | 
	
		
			
				|  |  | -    // 4. Recv trailing metadata, on_completion callback
 | 
	
		
			
				|  |  | -    started_ = true;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | -                   [this](bool ok) {
 | 
	
		
			
				|  |  | -                     reactor_->OnReadInitialMetadataDone(ok);
 | 
	
		
			
				|  |  | -                     MaybeFinish();
 | 
	
		
			
				|  |  | -                   },
 | 
	
		
			
				|  |  | -                   &start_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    // 4. Recv trailing metadata (unless corked)
 | 
	
		
			
				|  |  |      if (!start_corked_) {
 | 
	
		
			
				|  |  |        start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  |                                       context_->initial_metadata_flags());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    start_ops_.RecvInitialMetadata(context_);
 | 
	
		
			
				|  |  | -    start_ops_.set_core_cq_tag(&start_tag_);
 | 
	
		
			
				|  |  | -    call_.PerformOps(&start_ops_);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // Also set up the read and write tags so that they don't have to be set up
 | 
	
		
			
				|  |  | -    // each time
 | 
	
		
			
				|  |  | -    write_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | -                   [this](bool ok) {
 | 
	
		
			
				|  |  | -                     reactor_->OnWriteDone(ok);
 | 
	
		
			
				|  |  | -                     MaybeFinish();
 | 
	
		
			
				|  |  | -                   },
 | 
	
		
			
				|  |  | -                   &write_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | -    write_ops_.set_core_cq_tag(&write_tag_);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    read_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | -                  [this](bool ok) {
 | 
	
		
			
				|  |  | -                    reactor_->OnReadDone(ok);
 | 
	
		
			
				|  |  | -                    MaybeFinish();
 | 
	
		
			
				|  |  | -                  },
 | 
	
		
			
				|  |  | -                  &read_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | -    read_ops_.set_core_cq_tag(&read_tag_);
 | 
	
		
			
				|  |  | -    if (read_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if (write_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&start_ops_);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    if (writes_done_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      if (backlog_.read_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (backlog_.write_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (backlog_.writes_done_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      call_.PerformOps(&finish_ops_);
 | 
	
		
			
				|  |  | +      // The last thing in this critical section is to set started_ so that it
 | 
	
		
			
				|  |  | +      // can be used lock-free as well.
 | 
	
		
			
				|  |  | +      started_.store(true, std::memory_order_release);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    finish_tag_.Set(call_.call(), [this](bool /*ok*/) { MaybeFinish(); },
 | 
	
		
			
				|  |  | -                    &finish_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | -    finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  | -    finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  | -    call_.PerformOps(&finish_ops_);
 | 
	
		
			
				|  |  | +    // MaybeFinish outside the lock to make sure that destruction of this object
 | 
	
		
			
				|  |  | +    // doesn't take place while holding the lock (which would cause the lock to
 | 
	
		
			
				|  |  | +    // be released after destruction)
 | 
	
		
			
				|  |  | +    this->MaybeFinish(/*from_reaction=*/false);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void Read(Response* msg) override {
 | 
	
		
			
				|  |  |      read_ops_.RecvMessage(msg);
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      read_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.read_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void Write(const Request* msg, ::grpc::WriteOptions options) override {
 | 
	
		
			
				|  |  | -    if (start_corked_) {
 | 
	
		
			
				|  |  | -      write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | -                                     context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | -      start_corked_ = false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      if (options.is_last_message()) {
 | 
	
		
			
				|  |  |        options.set_buffer_hint();
 | 
	
		
			
				|  |  |        write_ops_.ClientSendClose();
 | 
	
	
		
			
				|  | @@ -538,38 +547,50 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |      // TODO(vjpai): don't assert
 | 
	
		
			
				|  |  |      GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(msg, options).ok());
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      write_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(corked_write_needed_)) {
 | 
	
		
			
				|  |  | +      write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | +                                     context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | +      corked_write_needed_ = false;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.write_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    void WritesDone() override {
 | 
	
		
			
				|  |  | -    if (start_corked_) {
 | 
	
		
			
				|  |  | -      writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | -                                           context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | -      start_corked_ = false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |      writes_done_ops_.ClientSendClose();
 | 
	
		
			
				|  |  |      writes_done_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  |                           [this](bool ok) {
 | 
	
		
			
				|  |  |                             reactor_->OnWritesDoneDone(ok);
 | 
	
		
			
				|  |  | -                           MaybeFinish();
 | 
	
		
			
				|  |  | +                           MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  |                           },
 | 
	
		
			
				|  |  |                           &writes_done_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      writes_done_ops_.set_core_cq_tag(&writes_done_tag_);
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      writes_done_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(corked_write_needed_)) {
 | 
	
		
			
				|  |  | +      writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | +                                           context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | +      corked_write_needed_ = false;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.writes_done_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void AddHold(int holds) override {
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(holds, std::memory_order_relaxed);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  void RemoveHold() override { MaybeFinish(); }
 | 
	
		
			
				|  |  | +  void RemoveHold() override { MaybeFinish(/*from_reaction=*/false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    friend class ClientCallbackReaderWriterFactory<Request, Response>;
 | 
	
	
		
			
				|  | @@ -580,8 +601,44 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |        : context_(context),
 | 
	
		
			
				|  |  |          call_(call),
 | 
	
		
			
				|  |  |          reactor_(reactor),
 | 
	
		
			
				|  |  | -        start_corked_(context_->initial_metadata_corked_) {
 | 
	
		
			
				|  |  | +        start_corked_(context_->initial_metadata_corked_),
 | 
	
		
			
				|  |  | +        corked_write_needed_(start_corked_) {
 | 
	
		
			
				|  |  |      this->BindReactor(reactor);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // Set up the unchanging parts of the start, read, and write tags and ops.
 | 
	
		
			
				|  |  | +    start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | +                   [this](bool ok) {
 | 
	
		
			
				|  |  | +                     reactor_->OnReadInitialMetadataDone(ok);
 | 
	
		
			
				|  |  | +                     MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  | +                   },
 | 
	
		
			
				|  |  | +                   &start_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    start_ops_.RecvInitialMetadata(context_);
 | 
	
		
			
				|  |  | +    start_ops_.set_core_cq_tag(&start_tag_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    write_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | +                   [this](bool ok) {
 | 
	
		
			
				|  |  | +                     reactor_->OnWriteDone(ok);
 | 
	
		
			
				|  |  | +                     MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  | +                   },
 | 
	
		
			
				|  |  | +                   &write_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    write_ops_.set_core_cq_tag(&write_tag_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    read_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | +                  [this](bool ok) {
 | 
	
		
			
				|  |  | +                    reactor_->OnReadDone(ok);
 | 
	
		
			
				|  |  | +                    MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  | +                  },
 | 
	
		
			
				|  |  | +                  &read_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    read_ops_.set_core_cq_tag(&read_tag_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // Also set up the Finish tag and op set.
 | 
	
		
			
				|  |  | +    finish_tag_.Set(
 | 
	
		
			
				|  |  | +        call_.call(),
 | 
	
		
			
				|  |  | +        [this](bool /*ok*/) { MaybeFinish(/*from_reaction=*/true); },
 | 
	
		
			
				|  |  | +        &finish_ops_,
 | 
	
		
			
				|  |  | +        /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  | +    finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    ::grpc_impl::ClientContext* const context_;
 | 
	
	
		
			
				|  | @@ -592,7 +649,9 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpRecvInitialMetadata>
 | 
	
		
			
				|  |  |        start_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag start_tag_;
 | 
	
		
			
				|  |  | -  bool start_corked_;
 | 
	
		
			
				|  |  | +  const bool start_corked_;
 | 
	
		
			
				|  |  | +  bool corked_write_needed_;  // no lock needed since only accessed in
 | 
	
		
			
				|  |  | +                              // Write/WritesDone which cannot be concurrent
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpClientRecvStatus> finish_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag finish_tag_;
 | 
	
	
		
			
				|  | @@ -603,22 +662,27 @@ class ClientCallbackReaderWriterImpl
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpClientSendClose>
 | 
	
		
			
				|  |  |        write_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag write_tag_;
 | 
	
		
			
				|  |  | -  bool write_ops_at_start_{false};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpSendInitialMetadata,
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpClientSendClose>
 | 
	
		
			
				|  |  |        writes_done_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag writes_done_tag_;
 | 
	
		
			
				|  |  | -  bool writes_done_ops_at_start_{false};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpRecvMessage<Response>>
 | 
	
		
			
				|  |  |        read_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag read_tag_;
 | 
	
		
			
				|  |  | -  bool read_ops_at_start_{false};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // Minimum of 2 callbacks to pre-register for start and finish
 | 
	
		
			
				|  |  | -  std::atomic<intptr_t> callbacks_outstanding_{2};
 | 
	
		
			
				|  |  | -  bool started_{false};
 | 
	
		
			
				|  |  | +  struct StartCallBacklog {
 | 
	
		
			
				|  |  | +    bool write_ops = false;
 | 
	
		
			
				|  |  | +    bool writes_done_ops = false;
 | 
	
		
			
				|  |  | +    bool read_ops = false;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  StartCallBacklog backlog_ /* GUARDED_BY(start_mu_) */;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Minimum of 3 callbacks to pre-register for start ops, StartCall, and finish
 | 
	
		
			
				|  |  | +  std::atomic<intptr_t> callbacks_outstanding_{3};
 | 
	
		
			
				|  |  | +  std::atomic_bool started_{false};
 | 
	
		
			
				|  |  | +  grpc::internal::Mutex start_mu_;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  template <class Request, class Response>
 | 
	
	
		
			
				|  | @@ -654,7 +718,8 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |    // there are no tests catching the compiler warning.
 | 
	
		
			
				|  |  |    static void operator delete(void*, void*) { GPR_CODEGEN_ASSERT(false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  void MaybeFinish() {
 | 
	
		
			
				|  |  | +  // MaybeFinish behaves as in ClientCallbackReaderWriterImpl.
 | 
	
		
			
				|  |  | +  void MaybeFinish(bool from_reaction) {
 | 
	
		
			
				|  |  |      if (GPR_UNLIKELY(callbacks_outstanding_.fetch_sub(
 | 
	
		
			
				|  |  |                           1, std::memory_order_acq_rel) == 1)) {
 | 
	
		
			
				|  |  |        ::grpc::Status s = std::move(finish_status_);
 | 
	
	
		
			
				|  | @@ -662,7 +727,11 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |        auto* call = call_.call();
 | 
	
		
			
				|  |  |        this->~ClientCallbackReaderImpl();
 | 
	
		
			
				|  |  |        ::grpc::g_core_codegen_interface->grpc_call_unref(call);
 | 
	
		
			
				|  |  | -      reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(from_reaction)) {
 | 
	
		
			
				|  |  | +        reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        reactor->InternalScheduleOnDone(std::move(s));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -670,13 +739,12 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |      // This call initiates two batches, plus any backlog, each with a callback
 | 
	
		
			
				|  |  |      // 1. Send initial metadata (unless corked) + recv initial metadata
 | 
	
		
			
				|  |  |      // 2. Any backlog
 | 
	
		
			
				|  |  | -    // 3. Recv trailing metadata, on_completion callback
 | 
	
		
			
				|  |  | -    started_ = true;
 | 
	
		
			
				|  |  | +    // 3. Recv trailing metadata
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  |                     [this](bool ok) {
 | 
	
		
			
				|  |  |                       reactor_->OnReadInitialMetadataDone(ok);
 | 
	
		
			
				|  |  | -                     MaybeFinish();
 | 
	
		
			
				|  |  | +                     MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  |                     },
 | 
	
		
			
				|  |  |                     &start_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
	
		
			
				|  | @@ -689,16 +757,23 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |      read_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  |                    [this](bool ok) {
 | 
	
		
			
				|  |  |                      reactor_->OnReadDone(ok);
 | 
	
		
			
				|  |  | -                    MaybeFinish();
 | 
	
		
			
				|  |  | +                    MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  |                    },
 | 
	
		
			
				|  |  |                    &read_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      read_ops_.set_core_cq_tag(&read_tag_);
 | 
	
		
			
				|  |  | -    if (read_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (backlog_.read_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      started_.store(true, std::memory_order_release);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    finish_tag_.Set(call_.call(), [this](bool /*ok*/) { MaybeFinish(); },
 | 
	
		
			
				|  |  | -                    &finish_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    finish_tag_.Set(
 | 
	
		
			
				|  |  | +        call_.call(),
 | 
	
		
			
				|  |  | +        [this](bool /*ok*/) { MaybeFinish(/*from_reaction=*/true); },
 | 
	
		
			
				|  |  | +        &finish_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  |      finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  |      call_.PerformOps(&finish_ops_);
 | 
	
	
		
			
				|  | @@ -707,17 +782,20 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |    void Read(Response* msg) override {
 | 
	
		
			
				|  |  |      read_ops_.RecvMessage(msg);
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      read_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.read_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&read_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void AddHold(int holds) override {
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(holds, std::memory_order_relaxed);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  void RemoveHold() override { MaybeFinish(); }
 | 
	
		
			
				|  |  | +  void RemoveHold() override { MaybeFinish(/*from_reaction=*/false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    friend class ClientCallbackReaderFactory<Response>;
 | 
	
	
		
			
				|  | @@ -752,11 +830,16 @@ class ClientCallbackReaderImpl : public ClientCallbackReader<Response> {
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpRecvMessage<Response>>
 | 
	
		
			
				|  |  |        read_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag read_tag_;
 | 
	
		
			
				|  |  | -  bool read_ops_at_start_{false};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  struct StartCallBacklog {
 | 
	
		
			
				|  |  | +    bool read_ops = false;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  StartCallBacklog backlog_ /* GUARDED_BY(start_mu_) */;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    // Minimum of 2 callbacks to pre-register for start and finish
 | 
	
		
			
				|  |  |    std::atomic<intptr_t> callbacks_outstanding_{2};
 | 
	
		
			
				|  |  | -  bool started_{false};
 | 
	
		
			
				|  |  | +  std::atomic_bool started_{false};
 | 
	
		
			
				|  |  | +  grpc::internal::Mutex start_mu_;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  template <class Response>
 | 
	
	
		
			
				|  | @@ -793,7 +876,8 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |    // there are no tests catching the compiler warning.
 | 
	
		
			
				|  |  |    static void operator delete(void*, void*) { GPR_CODEGEN_ASSERT(false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  void MaybeFinish() {
 | 
	
		
			
				|  |  | +  // MaybeFinish behaves as in ClientCallbackReaderWriterImpl.
 | 
	
		
			
				|  |  | +  void MaybeFinish(bool from_reaction) {
 | 
	
		
			
				|  |  |      if (GPR_UNLIKELY(callbacks_outstanding_.fetch_sub(
 | 
	
		
			
				|  |  |                           1, std::memory_order_acq_rel) == 1)) {
 | 
	
		
			
				|  |  |        ::grpc::Status s = std::move(finish_status_);
 | 
	
	
		
			
				|  | @@ -801,7 +885,11 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |        auto* call = call_.call();
 | 
	
		
			
				|  |  |        this->~ClientCallbackWriterImpl();
 | 
	
		
			
				|  |  |        ::grpc::g_core_codegen_interface->grpc_call_unref(call);
 | 
	
		
			
				|  |  | -      reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(from_reaction)) {
 | 
	
		
			
				|  |  | +        reactor->OnDone(s);
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        reactor->InternalScheduleOnDone(std::move(s));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -809,94 +897,90 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |      // This call initiates two batches, plus any backlog, each with a callback
 | 
	
		
			
				|  |  |      // 1. Send initial metadata (unless corked) + recv initial metadata
 | 
	
		
			
				|  |  |      // 2. Any backlog
 | 
	
		
			
				|  |  | -    // 3. Recv trailing metadata, on_completion callback
 | 
	
		
			
				|  |  | -    started_ = true;
 | 
	
		
			
				|  |  | +    // 3. Recv trailing metadata
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | -                   [this](bool ok) {
 | 
	
		
			
				|  |  | -                     reactor_->OnReadInitialMetadataDone(ok);
 | 
	
		
			
				|  |  | -                     MaybeFinish();
 | 
	
		
			
				|  |  | -                   },
 | 
	
		
			
				|  |  | -                   &start_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      if (!start_corked_) {
 | 
	
		
			
				|  |  |        start_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  |                                       context_->initial_metadata_flags());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    start_ops_.RecvInitialMetadata(context_);
 | 
	
		
			
				|  |  | -    start_ops_.set_core_cq_tag(&start_tag_);
 | 
	
		
			
				|  |  |      call_.PerformOps(&start_ops_);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // Also set up the read and write tags so that they don't have to be set up
 | 
	
		
			
				|  |  | -    // each time
 | 
	
		
			
				|  |  | -    write_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | -                   [this](bool ok) {
 | 
	
		
			
				|  |  | -                     reactor_->OnWriteDone(ok);
 | 
	
		
			
				|  |  | -                     MaybeFinish();
 | 
	
		
			
				|  |  | -                   },
 | 
	
		
			
				|  |  | -                   &write_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | -    write_ops_.set_core_cq_tag(&write_tag_);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if (write_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      if (backlog_.write_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (backlog_.writes_done_ops) {
 | 
	
		
			
				|  |  | +        call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      call_.PerformOps(&finish_ops_);
 | 
	
		
			
				|  |  | +      // The last thing in this critical section is to set started_ so that it
 | 
	
		
			
				|  |  | +      // can be used lock-free as well.
 | 
	
		
			
				|  |  | +      started_.store(true, std::memory_order_release);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if (writes_done_ops_at_start_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    finish_tag_.Set(call_.call(), [this](bool /*ok*/) { MaybeFinish(); },
 | 
	
		
			
				|  |  | -                    &finish_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | -    finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  | -    finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  | -    call_.PerformOps(&finish_ops_);
 | 
	
		
			
				|  |  | +    // MaybeFinish outside the lock to make sure that destruction of this object
 | 
	
		
			
				|  |  | +    // doesn't take place while holding the lock (which would cause the lock to
 | 
	
		
			
				|  |  | +    // be released after destruction)
 | 
	
		
			
				|  |  | +    this->MaybeFinish(/*from_reaction=*/false);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void Write(const Request* msg, ::grpc::WriteOptions options) override {
 | 
	
		
			
				|  |  | -    if (start_corked_) {
 | 
	
		
			
				|  |  | -      write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | -                                     context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | -      start_corked_ = false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if (options.is_last_message()) {
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(options.is_last_message())) {
 | 
	
		
			
				|  |  |        options.set_buffer_hint();
 | 
	
		
			
				|  |  |        write_ops_.ClientSendClose();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      // TODO(vjpai): don't assert
 | 
	
		
			
				|  |  |      GPR_CODEGEN_ASSERT(write_ops_.SendMessagePtr(msg, options).ok());
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      write_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(corked_write_needed_)) {
 | 
	
		
			
				|  |  | +      write_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | +                                     context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | +      corked_write_needed_ = false;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.write_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&write_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    void WritesDone() override {
 | 
	
		
			
				|  |  | -    if (start_corked_) {
 | 
	
		
			
				|  |  | -      writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | -                                           context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | -      start_corked_ = false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |      writes_done_ops_.ClientSendClose();
 | 
	
		
			
				|  |  |      writes_done_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  |                           [this](bool ok) {
 | 
	
		
			
				|  |  |                             reactor_->OnWritesDoneDone(ok);
 | 
	
		
			
				|  |  | -                           MaybeFinish();
 | 
	
		
			
				|  |  | +                           MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  |                           },
 | 
	
		
			
				|  |  |                           &writes_done_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  |      writes_done_ops_.set_core_cq_tag(&writes_done_tag_);
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(1, std::memory_order_relaxed);
 | 
	
		
			
				|  |  | -    if (started_) {
 | 
	
		
			
				|  |  | -      call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      writes_done_ops_at_start_ = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(corked_write_needed_)) {
 | 
	
		
			
				|  |  | +      writes_done_ops_.SendInitialMetadata(&context_->send_initial_metadata_,
 | 
	
		
			
				|  |  | +                                           context_->initial_metadata_flags());
 | 
	
		
			
				|  |  | +      corked_write_needed_ = false;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (GPR_UNLIKELY(!started_.load(std::memory_order_acquire))) {
 | 
	
		
			
				|  |  | +      grpc::internal::MutexLock lock(&start_mu_);
 | 
	
		
			
				|  |  | +      if (GPR_LIKELY(!started_.load(std::memory_order_relaxed))) {
 | 
	
		
			
				|  |  | +        backlog_.writes_done_ops = true;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    call_.PerformOps(&writes_done_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void AddHold(int holds) override {
 | 
	
		
			
				|  |  |      callbacks_outstanding_.fetch_add(holds, std::memory_order_relaxed);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  void RemoveHold() override { MaybeFinish(); }
 | 
	
		
			
				|  |  | +  void RemoveHold() override { MaybeFinish(/*from_reaction=*/false); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |   private:
 | 
	
		
			
				|  |  |    friend class ClientCallbackWriterFactory<Request>;
 | 
	
	
		
			
				|  | @@ -909,10 +993,38 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |        : context_(context),
 | 
	
		
			
				|  |  |          call_(call),
 | 
	
		
			
				|  |  |          reactor_(reactor),
 | 
	
		
			
				|  |  | -        start_corked_(context_->initial_metadata_corked_) {
 | 
	
		
			
				|  |  | +        start_corked_(context_->initial_metadata_corked_),
 | 
	
		
			
				|  |  | +        corked_write_needed_(start_corked_) {
 | 
	
		
			
				|  |  |      this->BindReactor(reactor);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // Set up the unchanging parts of the start and write tags and ops.
 | 
	
		
			
				|  |  | +    start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | +                   [this](bool ok) {
 | 
	
		
			
				|  |  | +                     reactor_->OnReadInitialMetadataDone(ok);
 | 
	
		
			
				|  |  | +                     MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  | +                   },
 | 
	
		
			
				|  |  | +                   &start_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    start_ops_.RecvInitialMetadata(context_);
 | 
	
		
			
				|  |  | +    start_ops_.set_core_cq_tag(&start_tag_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    write_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  | +                   [this](bool ok) {
 | 
	
		
			
				|  |  | +                     reactor_->OnWriteDone(ok);
 | 
	
		
			
				|  |  | +                     MaybeFinish(/*from_reaction=*/true);
 | 
	
		
			
				|  |  | +                   },
 | 
	
		
			
				|  |  | +                   &write_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    write_ops_.set_core_cq_tag(&write_tag_);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // Also set up the Finish tag and op set.
 | 
	
		
			
				|  |  |      finish_ops_.RecvMessage(response);
 | 
	
		
			
				|  |  |      finish_ops_.AllowNoMessage();
 | 
	
		
			
				|  |  | +    finish_tag_.Set(
 | 
	
		
			
				|  |  | +        call_.call(),
 | 
	
		
			
				|  |  | +        [this](bool /*ok*/) { MaybeFinish(/*from_reaction=*/true); },
 | 
	
		
			
				|  |  | +        &finish_ops_,
 | 
	
		
			
				|  |  | +        /*can_inline=*/false);
 | 
	
		
			
				|  |  | +    finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  | +    finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    ::grpc_impl::ClientContext* const context_;
 | 
	
	
		
			
				|  | @@ -923,7 +1035,9 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpRecvInitialMetadata>
 | 
	
		
			
				|  |  |        start_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag start_tag_;
 | 
	
		
			
				|  |  | -  bool start_corked_;
 | 
	
		
			
				|  |  | +  const bool start_corked_;
 | 
	
		
			
				|  |  | +  bool corked_write_needed_;  // no lock needed since only accessed in
 | 
	
		
			
				|  |  | +                              // Write/WritesDone which cannot be concurrent
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpGenericRecvMessage,
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpClientRecvStatus>
 | 
	
	
		
			
				|  | @@ -936,17 +1050,22 @@ class ClientCallbackWriterImpl : public ClientCallbackWriter<Request> {
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpClientSendClose>
 | 
	
		
			
				|  |  |        write_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag write_tag_;
 | 
	
		
			
				|  |  | -  bool write_ops_at_start_{false};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    grpc::internal::CallOpSet<grpc::internal::CallOpSendInitialMetadata,
 | 
	
		
			
				|  |  |                              grpc::internal::CallOpClientSendClose>
 | 
	
		
			
				|  |  |        writes_done_ops_;
 | 
	
		
			
				|  |  |    grpc::internal::CallbackWithSuccessTag writes_done_tag_;
 | 
	
		
			
				|  |  | -  bool writes_done_ops_at_start_{false};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // Minimum of 2 callbacks to pre-register for start and finish
 | 
	
		
			
				|  |  | -  std::atomic<intptr_t> callbacks_outstanding_{2};
 | 
	
		
			
				|  |  | -  bool started_{false};
 | 
	
		
			
				|  |  | +  struct StartCallBacklog {
 | 
	
		
			
				|  |  | +    bool write_ops = false;
 | 
	
		
			
				|  |  | +    bool writes_done_ops = false;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  StartCallBacklog backlog_ /* GUARDED_BY(start_mu_) */;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Minimum of 3 callbacks to pre-register for start ops, StartCall, and finish
 | 
	
		
			
				|  |  | +  std::atomic<intptr_t> callbacks_outstanding_{3};
 | 
	
		
			
				|  |  | +  std::atomic_bool started_{false};
 | 
	
		
			
				|  |  | +  grpc::internal::Mutex start_mu_;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  template <class Request>
 | 
	
	
		
			
				|  | @@ -985,7 +1104,6 @@ class ClientCallbackUnaryImpl final : public ClientCallbackUnary {
 | 
	
		
			
				|  |  |      // This call initiates two batches, each with a callback
 | 
	
		
			
				|  |  |      // 1. Send initial metadata + write + writes done + recv initial metadata
 | 
	
		
			
				|  |  |      // 2. Read message, recv trailing metadata
 | 
	
		
			
				|  |  | -    started_ = true;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      start_tag_.Set(call_.call(),
 | 
	
		
			
				|  |  |                     [this](bool ok) {
 | 
	
	
		
			
				|  | @@ -1000,12 +1118,16 @@ class ClientCallbackUnaryImpl final : public ClientCallbackUnary {
 | 
	
		
			
				|  |  |      call_.PerformOps(&start_ops_);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      finish_tag_.Set(call_.call(), [this](bool /*ok*/) { MaybeFinish(); },
 | 
	
		
			
				|  |  | -                    &finish_ops_, /*can_inline=*/false);
 | 
	
		
			
				|  |  | +                    &finish_ops_,
 | 
	
		
			
				|  |  | +                    /*can_inline=*/false);
 | 
	
		
			
				|  |  |      finish_ops_.ClientRecvStatus(context_, &finish_status_);
 | 
	
		
			
				|  |  |      finish_ops_.set_core_cq_tag(&finish_tag_);
 | 
	
		
			
				|  |  |      call_.PerformOps(&finish_ops_);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  // In the unary case, MaybeFinish is only ever invoked from a
 | 
	
		
			
				|  |  | +  // library-initiated reaction, so it will just directly call OnDone if this is
 | 
	
		
			
				|  |  | +  // the last reaction for this RPC.
 | 
	
		
			
				|  |  |    void MaybeFinish() {
 | 
	
		
			
				|  |  |      if (GPR_UNLIKELY(callbacks_outstanding_.fetch_sub(
 | 
	
		
			
				|  |  |                           1, std::memory_order_acq_rel) == 1)) {
 | 
	
	
		
			
				|  | @@ -1053,7 +1175,6 @@ class ClientCallbackUnaryImpl final : public ClientCallbackUnary {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    // This call will have 2 callbacks: start and finish
 | 
	
		
			
				|  |  |    std::atomic<intptr_t> callbacks_outstanding_{2};
 | 
	
		
			
				|  |  | -  bool started_{false};
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  class ClientCallbackUnaryFactory {
 |