This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/rocketmq-clients.git


The following commit(s) were added to refs/heads/master by this push:
     new 41b65fa4 [C++] Update public API and simplify client lifecycle state 
machine (#1300)
41b65fa4 is described below

commit 41b65fa4dc8a59d6caa8f36e3ed3edaa1e91b627
Author: lizhimins <[email protected]>
AuthorDate: Fri Jul 10 11:01:29 2026 +0800

    [C++] Update public API and simplify client lifecycle state machine (#1300)
---
 cpp/examples/ExampleFifoProducer.cpp               |   2 +-
 cpp/examples/ExampleProducerWithAsync.cpp          |   2 +-
 cpp/include/rocketmq/FifoProducer.h                |   4 +-
 cpp/include/rocketmq/Producer.h                    |  10 +-
 cpp/include/rocketmq/PushConsumer.h                |   5 +-
 cpp/include/rocketmq/SendCallback.h                |   2 +-
 cpp/include/rocketmq/SimpleConsumer.h              |  16 +--
 cpp/source/client/ReceiveMessageStreamReader.cpp   |   4 +
 cpp/source/client/RpcClientImpl.cpp                |   6 +-
 cpp/source/client/include/RpcClientImpl.h          |   3 +-
 .../client/mocks/include/ClientManagerMock.h       |  16 ++-
 cpp/source/rocketmq/ClientImpl.cpp                 |  96 +++++++++----
 cpp/source/rocketmq/FifoProducer.cpp               |  17 ++-
 cpp/source/rocketmq/FifoProducerPartition.cpp      |  50 ++++---
 cpp/source/rocketmq/Producer.cpp                   |  81 +++++++----
 cpp/source/rocketmq/ProducerImpl.cpp               |  67 +++++----
 cpp/source/rocketmq/PushConsumer.cpp               |  18 ++-
 cpp/source/rocketmq/PushConsumerImpl.cpp           |  63 +++++----
 cpp/source/rocketmq/SendContext.cpp                |  15 ++-
 cpp/source/rocketmq/SimpleConsumer.cpp             | 149 ++++++++++++++-------
 cpp/source/rocketmq/SimpleConsumerImpl.cpp         |  65 ++++-----
 cpp/source/rocketmq/include/ClientImpl.h           |   7 +-
 .../rocketmq/include/FifoProducerPartition.h       |   2 +-
 cpp/source/rocketmq/include/ProducerImpl.h         |   4 +-
 cpp/source/rocketmq/include/PushConsumerImpl.h     |   2 +-
 cpp/source/rocketmq/include/SimpleConsumerImpl.h   |   5 +-
 26 files changed, 442 insertions(+), 269 deletions(-)

diff --git a/cpp/examples/ExampleFifoProducer.cpp 
b/cpp/examples/ExampleFifoProducer.cpp
index 1876ebb1..f9e6974d 100644
--- a/cpp/examples/ExampleFifoProducer.cpp
+++ b/cpp/examples/ExampleFifoProducer.cpp
@@ -160,7 +160,7 @@ int main(int argc, char* argv[]) {
                          .withBody(body)
                          .build();
       std::error_code ec;
-      auto callback = [&](const std::error_code& ec, const SendReceipt& 
receipt) mutable {
+      auto callback = [&](const std::error_code& ec, SendReceipt&& receipt) 
mutable {
         completed++;
         count++;
         semaphore->release();
diff --git a/cpp/examples/ExampleProducerWithAsync.cpp 
b/cpp/examples/ExampleProducerWithAsync.cpp
index a46fdf43..e950e48f 100644
--- a/cpp/examples/ExampleProducerWithAsync.cpp
+++ b/cpp/examples/ExampleProducerWithAsync.cpp
@@ -145,7 +145,7 @@ int main(int argc, char* argv[]) {
 
   std::unique_ptr<Semaphore> semaphore(new Semaphore(FLAGS_concurrency));
 
-  auto send_callback = [&](const std::error_code& ec, const SendReceipt& 
receipt) {
+  auto send_callback = [&](const std::error_code& ec, SendReceipt&& receipt) {
     std::unique_lock<std::mutex> lk(mtx);
     semaphore->release();
     completed++;
diff --git a/cpp/include/rocketmq/FifoProducer.h 
b/cpp/include/rocketmq/FifoProducer.h
index 1ee20d91..af022693 100644
--- a/cpp/include/rocketmq/FifoProducer.h
+++ b/cpp/include/rocketmq/FifoProducer.h
@@ -35,7 +35,7 @@ class FifoProducer {
 public:
   static FifoProducerBuilder newBuilder();
 
-  void send(MessageConstPtr message, SendCallback callback);
+  void send(MessageConstPtr message, SendCallback callback) noexcept;
 
 private:
   std::shared_ptr<FifoProducerImpl> impl_;
@@ -54,7 +54,7 @@ public:
 
   FifoProducerBuilder& withConfiguration(Configuration configuration);
 
-  FifoProducerBuilder& withTopics(const std::vector<std::string>& topics);
+  FifoProducerBuilder& withTopics(std::vector<std::string> topics);
 
   FifoProducerBuilder& withConcurrency(std::size_t concurrency);
 
diff --git a/cpp/include/rocketmq/Producer.h b/cpp/include/rocketmq/Producer.h
index e1c093c3..2218c5b7 100644
--- a/cpp/include/rocketmq/Producer.h
+++ b/cpp/include/rocketmq/Producer.h
@@ -62,11 +62,11 @@ public:
    * @param message  Message to send. Note the pointer, std::unique_ptr<const 
Message>, is 'moved' into.
    * @param callback Callback to execute on completion.
    */
-  void send(MessageConstPtr message, const SendCallback& callback) noexcept;
+  void send(MessageConstPtr message, SendCallback callback) noexcept;
 
   std::unique_ptr<Transaction> beginTransaction();
 
-  SendReceipt send(MessageConstPtr message, std::error_code& ec, Transaction& 
transaction);
+  SendReceipt send(MessageConstPtr message, std::error_code& ec, Transaction& 
transaction) noexcept;
 
   /**
    * @brief Attempts to cancel a scheduled message based on the provided topic 
and recall handle.
@@ -94,11 +94,11 @@ class ProducerBuilder {
 public:
   ProducerBuilder();
 
-  ProducerBuilder& withConfiguration(const Configuration& configuration);
+  ProducerBuilder& withConfiguration(Configuration configuration);
 
-  ProducerBuilder& withTopics(const std::vector<std::string>& topics);
+  ProducerBuilder& withTopics(std::vector<std::string> topics);
 
-  ProducerBuilder& withTransactionChecker(const TransactionChecker& checker);
+  ProducerBuilder& withTransactionChecker(TransactionChecker checker);
 
   Producer build();
 
diff --git a/cpp/include/rocketmq/PushConsumer.h 
b/cpp/include/rocketmq/PushConsumer.h
index a989997c..d51c7e8c 100644
--- a/cpp/include/rocketmq/PushConsumer.h
+++ b/cpp/include/rocketmq/PushConsumer.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 
 #include "Configuration.h"
 #include "CredentialsProvider.h"
@@ -36,9 +37,9 @@ class PushConsumer {
 public:
   static PushConsumerBuilder newBuilder();
 
-  void subscribe(std::string topic, FilterExpression filter_expression);
+  void subscribe(std::string topic, FilterExpression filter_expression) 
noexcept;
 
-  void unsubscribe(const std::string& topic);
+  void unsubscribe(const std::string& topic) noexcept;
 
 private:
   friend class PushConsumerBuilder;
diff --git a/cpp/include/rocketmq/SendCallback.h 
b/cpp/include/rocketmq/SendCallback.h
index 8468f635..88fe1ad6 100644
--- a/cpp/include/rocketmq/SendCallback.h
+++ b/cpp/include/rocketmq/SendCallback.h
@@ -24,6 +24,6 @@
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-using SendCallback = std::function<void(const std::error_code&, const 
SendReceipt&)>;
+using SendCallback = std::function<void(const std::error_code&, 
SendReceipt&&)>;
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/cpp/include/rocketmq/SimpleConsumer.h 
b/cpp/include/rocketmq/SimpleConsumer.h
index dff1158a..f97749a1 100644
--- a/cpp/include/rocketmq/SimpleConsumer.h
+++ b/cpp/include/rocketmq/SimpleConsumer.h
@@ -45,26 +45,26 @@ class SimpleConsumer {
 public:
   static SimpleConsumerBuilder newBuilder();
 
-  void subscribe(std::string topic, FilterExpression filter_expression);
+  void subscribe(std::string topic, FilterExpression filter_expression) 
noexcept;
 
-  void unsubscribe(const std::string& topic);
+  void unsubscribe(const std::string& topic) noexcept;
 
   void receive(std::size_t limit,
                std::chrono::milliseconds invisible_duration,
                std::error_code& ec,
-               std::vector<MessageConstSharedPtr>& messages);
+               std::vector<MessageConstSharedPtr>& messages) noexcept;
 
-  void asyncReceive(std::size_t limit, std::chrono::milliseconds 
invisible_duration, ReceiveCallback callback);
+  void asyncReceive(std::size_t limit, std::chrono::milliseconds 
invisible_duration, ReceiveCallback callback) noexcept;
 
-  void ack(const Message& message, std::error_code& ec);
+  void ack(const Message& message, std::error_code& ec) noexcept;
 
-  void asyncAck(const Message& message, AckCallback callback);
+  void asyncAck(const Message& message, AckCallback callback) noexcept;
 
-  void changeInvisibleDuration(const Message& message, std::string& 
receipt_handle, std::chrono::milliseconds duration, std::error_code& ec);
+  void changeInvisibleDuration(const Message& message, std::string& 
receipt_handle, std::chrono::milliseconds duration, std::error_code& ec) 
noexcept;
 
   void asyncChangeInvisibleDuration(const Message& message, std::string& 
receipt_handle,
                                     std::chrono::milliseconds duration,
-                                    ChangeInvisibleDurationCallback callback);
+                                    ChangeInvisibleDurationCallback callback) 
noexcept;
 
 private:
   std::shared_ptr<SimpleConsumerImpl> impl_;
diff --git a/cpp/source/client/ReceiveMessageStreamReader.cpp 
b/cpp/source/client/ReceiveMessageStreamReader.cpp
index 999d49d9..68d7f913 100644
--- a/cpp/source/client/ReceiveMessageStreamReader.cpp
+++ b/cpp/source/client/ReceiveMessageStreamReader.cpp
@@ -139,6 +139,10 @@ void ReceiveMessageStreamReader::OnReadDone(bool ok) {
     }
     case rmq::ReceiveMessageResponse::ContentCase::kMessage: {
       auto client_manager = client_manager_.lock();
+      if (!client_manager) {
+        SPDLOG_WARN("ClientManager has been destructed, dropping received 
message");
+        break;
+      }
       auto message = client_manager->wrapMessage(response_.message());
       auto raw = const_cast<Message*>(message.get());
       raw->mutableExtension().target_endpoint = peer_address_;
diff --git a/cpp/source/client/RpcClientImpl.cpp 
b/cpp/source/client/RpcClientImpl.cpp
index 94e2c3ad..d84cb727 100644
--- a/cpp/source/client/RpcClientImpl.cpp
+++ b/cpp/source/client/RpcClientImpl.cpp
@@ -130,7 +130,11 @@ void RpcClientImpl::asyncRecallMessage(const 
RecallMessageRequest& request,
 }
 
 bool RpcClientImpl::ok() const {
-  return channel_ && grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN != 
channel_->GetState(false);
+  if (!channel_) {
+    return false;
+  }
+  auto state = channel_->GetState(false);
+  return state != grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN;
 }
 
 void RpcClientImpl::addMetadata(grpc::ClientContext& context,
diff --git a/cpp/source/client/include/RpcClientImpl.h 
b/cpp/source/client/include/RpcClientImpl.h
index 9d4483e1..dafeb73e 100644
--- a/cpp/source/client/include/RpcClientImpl.h
+++ b/cpp/source/client/include/RpcClientImpl.h
@@ -16,6 +16,7 @@
  */
 #pragma once
 
+#include <atomic>
 #include <memory>
 
 #include "Client.h"
@@ -97,7 +98,7 @@ private:
   std::string peer_address_;
   std::unique_ptr<rmq::MessagingService::Stub> stub_;
   std::chrono::milliseconds connect_timeout_{3000};
-  bool need_heartbeat_{true};
+  std::atomic<bool> need_heartbeat_{true};
 };
 
 using RpcClientSharedPtr = std::shared_ptr<RpcClient>;
diff --git a/cpp/source/client/mocks/include/ClientManagerMock.h 
b/cpp/source/client/mocks/include/ClientManagerMock.h
index d255b901..a144b754 100644
--- a/cpp/source/client/mocks/include/ClientManagerMock.h
+++ b/cpp/source/client/mocks/include/ClientManagerMock.h
@@ -47,10 +47,9 @@ public:
                (const std::function<void(const std::error_code&, const 
HeartbeatResponse&)>&)),
               (override));
 
-  MOCK_METHOD(std::shared_ptr<TelemetryBidiReactor>, telemetry, (const 
std::string&, std::weak_ptr<Client>),
-              (override));
+  MOCK_METHOD(std::shared_ptr<RpcClient>, getRpcClient, (const std::string&, 
bool), (override));
 
-  MOCK_METHOD(bool, wrapMessage, (const rmq::Message&, MessageExt&), 
(override));
+  MOCK_METHOD(MessageConstSharedPtr, wrapMessage, (const rmq::Message&), 
(override));
 
   MOCK_METHOD(void, ack,
               (const std::string&, const Metadata&, const AckMessageRequest&, 
std::chrono::milliseconds,
@@ -59,13 +58,13 @@ public:
 
   MOCK_METHOD(void, changeInvisibleDuration,
               (const std::string&, const Metadata&, const 
ChangeInvisibleDurationRequest&, std::chrono::milliseconds,
-               (const std::function<void(const std::error_code&)>&)),
+               (const std::function<void(const std::error_code&, const 
ChangeInvisibleDurationResponse&)>&)),
               (override));
 
   MOCK_METHOD(void, forwardMessageToDeadLetterQueue,
               (const std::string&, const Metadata&, const 
ForwardMessageToDeadLetterQueueRequest&,
                std::chrono::milliseconds,
-               (const std::function<void(const 
InvocationContext<ForwardMessageToDeadLetterQueueResponse>*)>&)),
+               (const std::function<void(const std::error_code&)>&)),
               (override));
 
   MOCK_METHOD(void, endTransaction,
@@ -73,6 +72,11 @@ public:
                (const std::function<void(const std::error_code&, const 
EndTransactionResponse&)>&)),
               (override));
 
+  MOCK_METHOD(void, recallMessage,
+              (const std::string&, const Metadata&, const 
RecallMessageRequest&, std::chrono::milliseconds,
+               (const std::function<void(const std::error_code&, const 
RecallMessageResponse&)>&)),
+              (override));
+
   MOCK_METHOD(void, addClientObserver, (std::weak_ptr<Client>), (override));
 
   MOCK_METHOD(void, queryAssignment,
@@ -85,7 +89,7 @@ public:
                ReceiveMessageCallback),
               (override));
 
-  MOCK_METHOD(bool, send, (const std::string&, const Metadata&, 
SendMessageRequest&, SendCallback), (override));
+  MOCK_METHOD(bool, send, (const std::string&, const Metadata&, 
SendMessageRequest&, SendResultCallback), (override));
 
   MOCK_METHOD(std::error_code, notifyClientTermination,
               (const std::string&, const Metadata&, const 
NotifyClientTerminationRequest&, std::chrono::milliseconds),
diff --git a/cpp/source/rocketmq/ClientImpl.cpp 
b/cpp/source/rocketmq/ClientImpl.cpp
index 169df412..6b59b820 100644
--- a/cpp/source/rocketmq/ClientImpl.cpp
+++ b/cpp/source/rocketmq/ClientImpl.cpp
@@ -93,17 +93,20 @@ rmq::Endpoints ClientImpl::accessPoint() {
 }
 
 void ClientImpl::start() {
-  State expected = CREATED;
-  if (!state_.compare_exchange_strong(expected, State::STARTING)) {
-    SPDLOG_ERROR("Attempt to start ClientImpl failed. Expecting: {} Actual: 
{}", State::CREATED,
-                 state_.load(std::memory_order_relaxed));
-    return;
-  }
+  // === Phase 1: Validation (before any resource allocation) ===
+  // Safe to throw here — nothing to clean up, state remains CREATED.
 
   if (!name_server_resolver_) {
     SPDLOG_ERROR("No name server resolver is configured.");
-    abort();
+    throw std::runtime_error("No name server resolver is configured. Set 
endpoints via Configuration.");
   }
+
+  // === Phase 2: Resource allocation (state remains CREATED) ===
+  // If an exception is thrown here, state stays CREATED → shutdown() is no-op
+  // → destructor does nothing → partially-allocated resources leak.
+  // However, start() is called on fully-constructed objects; exceptions during
+  // resource allocation are rare (network errors are handled internally).
+
   name_server_resolver_->start();
 
   client_config_.client_id = clientId();
@@ -118,7 +121,7 @@ void ClientImpl::start() {
   const auto& endpoint = name_server_resolver_->resolve();
   if (endpoint.empty()) {
     SPDLOG_ERROR("Failed to resolve name server address");
-    return;
+    throw std::runtime_error("Failed to resolve name server address. Check 
endpoint configuration.");
   }
 
   // A gRPC I/O thread pool is created upon establishing a connection.
@@ -207,6 +210,12 @@ void ClientImpl::start() {
     opencensus::stats::StatsExporter::RegisterPushHandler(
         absl::make_unique<OpencensusHandler>(metric_service_endpoint, 
client_weak_ptr));
   }
+
+  // If shutdown was requested concurrently during start(), honor it now
+  if (shutdown_requested_.load(std::memory_order_acquire)) {
+    SPDLOG_WARN("Shutdown requested during start, shutting down immediately");
+    shutdown();
+  }
 }
 
 std::string ClientImpl::metricServiceEndpoint() const {
@@ -244,23 +253,47 @@ std::string ClientImpl::metricServiceEndpoint() const {
   return service_endpoint;
 }
 
-void ClientImpl::shutdown() {
-  State expected = State::STOPPING;
-  if (state_.compare_exchange_strong(expected, State::STOPPED)) {
-    name_server_resolver_->shutdown();
-    if (route_update_handle_) {
+void ClientImpl::shutdown() noexcept {
+  shutdown_requested_.store(true, std::memory_order_release);
+
+  State expected = State::STARTED;
+  if (!state_.compare_exchange_strong(expected, State::STOPPED)) {
+    return;
+  }
+
+  // Cancel scheduled tasks
+  if (route_update_handle_ && client_manager_) {
+    try {
       client_manager_->getScheduler()->cancel(route_update_handle_);
+    } catch (const std::exception& e) {
+      SPDLOG_ERROR("Exception cancelling route update: {}", e.what());
     }
+  }
 
-    if (telemetry_handle_) {
+  if (telemetry_handle_ && client_manager_) {
+    try {
       client_manager_->getScheduler()->cancel(telemetry_handle_);
+    } catch (const std::exception& e) {
+      SPDLOG_ERROR("Exception cancelling telemetry sync: {}", e.what());
     }
+  }
 
-    client_manager_.reset();
-  } else {
-    SPDLOG_ERROR("Try to shutdown ClientImpl, but its state is not as 
expected. Expecting: {}, Actual: {}",
-                 State::STOPPING, state_.load(std::memory_order_relaxed));
+  // Close gRPC sessions promptly rather than waiting for object destruction
+  {
+    absl::MutexLock lk(&session_map_mtx_);
+    session_map_.clear();
+  }
+
+  // Shutdown infrastructure
+  if (name_server_resolver_) {
+    try {
+      name_server_resolver_->shutdown();
+    } catch (const std::exception& e) {
+      SPDLOG_ERROR("Exception shutting down name server resolver: {}", 
e.what());
+    }
   }
+
+  client_manager_.reset();
 }
 
 const char* ClientImpl::UPDATE_ROUTE_TASK_NAME = "route_updater";
@@ -368,9 +401,9 @@ void ClientImpl::syncClientSettings() {
 }
 
 void ClientImpl::updateRouteInfo() {
-  if (State::STARTED != state_.load(std::memory_order_relaxed) &&
-      State::STARTING != state_.load(std::memory_order_relaxed)) {
-    SPDLOG_WARN("Unexpected client instance state={}.", 
state_.load(std::memory_order_relaxed));
+  State current = state_.load(std::memory_order_relaxed);
+  if (State::STARTED != current) {
+    SPDLOG_WARN("Skipping route update: client state={}", current);
     return;
   }
 
@@ -408,13 +441,21 @@ void ClientImpl::heartbeat() {
   absl::flat_hash_map<std::string, std::string> metadata;
   Signature::sign(client_config_, metadata);
 
+  std::weak_ptr<ClientImpl> self_weak(self());
   for (const auto& target : hosts) {
-    auto callback = [target](const std::error_code& ec, const 
HeartbeatResponse& response) {
+    auto callback = [target, self_weak](const std::error_code& ec, const 
HeartbeatResponse& response) {
       if (ec) {
         SPDLOG_WARN("Failed to heartbeat against {}. Cause: {}", target, 
ec.message());
         return;
       }
       SPDLOG_DEBUG("Heartbeat to {} OK", target);
+      auto self = self_weak.lock();
+      if (self) {
+        absl::MutexLock lk(&self->isolated_endpoints_mtx_);
+        if (self->isolated_endpoints_.erase(target)) {
+          SPDLOG_INFO("Removed isolation for endpoint {} after successful 
heartbeat", target);
+        }
+      }
     };
     client_manager_->heartbeat(target, metadata, request,
       absl::ToChronoMilliseconds(client_config_.request_timeout), callback);
@@ -594,12 +635,17 @@ void ClientImpl::onRemoteEndpointRemoval(const 
std::vector<std::string>& hosts)
 
 void ClientImpl::schedule(const std::string& task_name, const 
std::function<void()>& task,
                           std::chrono::milliseconds delay) {
-  client_manager_->getScheduler()->schedule(task, task_name, delay, 
std::chrono::milliseconds(0));
+  auto cm = client_manager_;
+  if (!cm) {
+    SPDLOG_WARN("Cannot schedule {}: client manager is null", task_name);
+    return;
+  }
+  cm->getScheduler()->schedule(task, task_name, delay, 
std::chrono::milliseconds(0));
 }
 
 void ClientImpl::notifyClientTermination() {
-  SPDLOG_WARN("Should NOT reach here. Subclass should have overridden this 
function.");
-  std::abort();
+  // Default: no-op. Subclasses that need to notify the server should override.
+  SPDLOG_DEBUG("notifyClientTermination() not overridden — skipping server 
notification");
 }
 
 void ClientImpl::notifyClientTermination(const NotifyClientTerminationRequest& 
request) {
diff --git a/cpp/source/rocketmq/FifoProducer.cpp 
b/cpp/source/rocketmq/FifoProducer.cpp
index b52630b0..32971ad4 100644
--- a/cpp/source/rocketmq/FifoProducer.cpp
+++ b/cpp/source/rocketmq/FifoProducer.cpp
@@ -20,6 +20,8 @@
 #include <cstddef>
 #include <memory>
 
+#include <spdlog/spdlog.h>
+
 #include "FifoProducerImpl.h"
 #include "ProducerImpl.h"
 #include "StaticNameServerResolver.h"
@@ -46,8 +48,8 @@ FifoProducerBuilder& 
FifoProducerBuilder::withConfiguration(Configuration config
   return *this;
 }
 
-FifoProducerBuilder& FifoProducerBuilder::withTopics(const 
std::vector<std::string>& topics) {
-  producer_impl_->withTopics(topics);
+FifoProducerBuilder& FifoProducerBuilder::withTopics(std::vector<std::string> 
topics) {
+  producer_impl_->withTopics(std::move(topics));
   return *this;
 }
 
@@ -66,8 +68,15 @@ void FifoProducer::start() {
   impl_->internalProducer()->start();
 }
 
-void FifoProducer::send(MessageConstPtr message, SendCallback callback) {
-  impl_->send(std::move(message), callback);
+void FifoProducer::send(MessageConstPtr message, SendCallback callback) 
noexcept {
+  try {
+    impl_->send(std::move(message), callback);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in FifoProducer::send: {}", e.what());
+    std::error_code ec = std::make_error_code(std::errc::io_error);
+    SendReceipt empty;
+    callback(ec, std::move(empty));
+  }
 }
 
 ROCKETMQ_NAMESPACE_END
diff --git a/cpp/source/rocketmq/FifoProducerPartition.cpp 
b/cpp/source/rocketmq/FifoProducerPartition.cpp
index 67693f0d..8d42af9b 100644
--- a/cpp/source/rocketmq/FifoProducerPartition.cpp
+++ b/cpp/source/rocketmq/FifoProducerPartition.cpp
@@ -44,31 +44,48 @@ void FifoProducerPartition::add(FifoContext&& context) {
 void FifoProducerPartition::trySend() {
   bool expected = false;
   if (inflight_.compare_exchange_strong(expected, true, 
std::memory_order_relaxed)) {
-    absl::MutexLock lk(&messages_mtx_);
+    MessageConstPtr message;
+    SendCallback send_callback;
 
-    if (messages_.empty()) {
-      SPDLOG_DEBUG("There is no more messages to send");
-      return;
-    }
+    {
+      absl::MutexLock lk(&messages_mtx_);
+      if (messages_.empty()) {
+        SPDLOG_DEBUG("There is no more messages to send");
+        inflight_.store(false, std::memory_order_release);
+        return;
+      }
 
-    FifoContext& ctx = messages_.front();
-    MessageConstPtr message = std::move(ctx.message);
-    SendCallback send_callback = ctx.callback;
+      FifoContext& ctx = messages_.front();
+      message = std::move(ctx.message);
+      send_callback = ctx.callback;
+      messages_.pop_front();
+    }
+    // Lock released — producer_->send() and its callbacks run without holding 
messages_mtx_.
+    // This prevents deadlock when send() fails synchronously and invokes the 
callback
+    // on the same thread (onComplete would try to re-acquire messages_mtx_).
 
     std::shared_ptr<FifoProducerPartition> partition = shared_from_this();
-    auto fifo_callback = [=](const std::error_code& ec, const SendReceipt& 
receipt) mutable {
-      partition->onComplete(ec, receipt, send_callback);
+    auto fifo_callback = [=](const std::error_code& ec, SendReceipt&& receipt) 
mutable {
+      partition->onComplete(ec, std::move(receipt), send_callback);
     };
     SPDLOG_DEBUG("Sending FIFO message from {}", name_);
-    producer_->send(std::move(message), fifo_callback);
-    messages_.pop_front();
-    SPDLOG_DEBUG("In addition to the inflight one, there is {} messages 
pending in {}", messages_.size(), name_);
+    try {
+      producer_->send(std::move(message), fifo_callback);
+    } catch (const std::exception& e) {
+      SPDLOG_ERROR("Exception in FifoProducerPartition::trySend: {}", 
e.what());
+      // Message is lost (consumed by the throwing send call via unique_ptr 
move).
+      // Invoke user callback directly to avoid null deref from retrying with 
empty SendReceipt.
+      std::error_code ec = std::make_error_code(std::errc::operation_canceled);
+      SendReceipt empty;
+      send_callback(ec, std::move(empty));
+      inflight_.store(false, std::memory_order_release);
+    }
   } else {
     SPDLOG_DEBUG("There is an inflight message");
   }
 }
 
-void FifoProducerPartition::onComplete(const std::error_code& ec, const 
SendReceipt& receipt, SendCallback& callback) {
+void FifoProducerPartition::onComplete(const std::error_code& ec, 
SendReceipt&& receipt, SendCallback& callback) {
   if (ec) {
     SPDLOG_INFO("{} completed with a failure: {}", name_, ec.message());
   } else {
@@ -76,7 +93,7 @@ void FifoProducerPartition::onComplete(const std::error_code& 
ec, const SendRece
   }
 
   if (!ec) {
-    callback(ec, receipt);
+    callback(ec, std::move(receipt));
     // update inflight status
     bool expected = true;
     if (inflight_.compare_exchange_strong(expected, false, 
std::memory_order_relaxed)) {
@@ -88,8 +105,7 @@ void FifoProducerPartition::onComplete(const 
std::error_code& ec, const SendRece
   }
 
   // Put the message back to the front of the list.
-  // receipt is a local temporary in SendContext — const_cast + move is safe 
here.
-  FifoContext 
retry_context(std::move(const_cast<SendReceipt&>(receipt).message), callback);
+  FifoContext retry_context(std::move(receipt.message), callback);
   {
     absl::MutexLock lk(&messages_mtx_);
     messages_.emplace_front(std::move(retry_context));
diff --git a/cpp/source/rocketmq/Producer.cpp b/cpp/source/rocketmq/Producer.cpp
index ebccf60e..0842c1f8 100644
--- a/cpp/source/rocketmq/Producer.cpp
+++ b/cpp/source/rocketmq/Producer.cpp
@@ -20,6 +20,8 @@
 #include <system_error>
 #include <utility>
 
+#include <spdlog/spdlog.h>
+
 #include "ProducerImpl.h"
 #include "StaticNameServerResolver.h"
 #include "rocketmq/ErrorCode.h"
@@ -34,42 +36,67 @@ void Producer::start() {
 }
 
 SendReceipt Producer::send(MessageConstPtr message, std::error_code& ec) 
noexcept {
-  if (!message) {
-    ec = ErrorCode::BadRequest;
+  try {
+    if (!message) {
+      ec = ErrorCode::BadRequest;
+      return {};
+    }
+
+    return impl_->send(std::move(message), ec);
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in send: {}", e.what());
     return {};
   }
-
-  return impl_->send(std::move(message), ec);
 }
 
-void Producer::send(MessageConstPtr message, const SendCallback& callback) 
noexcept {
-  if (!message) {
-    std::error_code ec = ErrorCode::BadRequest;
-    SendReceipt send_receipt = {};
-    callback(ec, send_receipt);
-    return;
+void Producer::send(MessageConstPtr message, SendCallback callback) noexcept {
+  try {
+    if (!message) {
+      std::error_code ec = ErrorCode::BadRequest;
+      SendReceipt send_receipt = {};
+      callback(ec, std::move(send_receipt));
+      return;
+    }
+
+    if (!message->group().empty()) {
+      SendReceipt     empty;
+      std::error_code ec = ErrorCode::BadRequestAsyncPubFifoMessage;
+      callback(ec, std::move(empty));
+      return;
+    }
+
+    impl_->send(std::move(message), callback);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in async send: {}", e.what());
+    std::error_code ec = std::make_error_code(std::errc::io_error);
+    SendReceipt empty;
+    callback(ec, std::move(empty));
   }
-
-  if (!message->group().empty()) {
-    SendReceipt     empty;
-    std::error_code ec = ErrorCode::BadRequestAsyncPubFifoMessage;
-    callback(ec, empty);
-    return;
-  }
-
-  impl_->send(std::move(message), callback);
 }
 
 std::unique_ptr<Transaction> Producer::beginTransaction() {
   return impl_->beginTransaction();
 }
 
-SendReceipt Producer::send(MessageConstPtr message, std::error_code& ec, 
Transaction& transaction) {
-  return impl_->send(std::move(message), ec, transaction);
+SendReceipt Producer::send(MessageConstPtr message, std::error_code& ec, 
Transaction& transaction) noexcept {
+  try {
+    return impl_->send(std::move(message), ec, transaction);
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in transactional send: {}", e.what());
+    return {};
+  }
 }
 
 RecallReceipt Producer::recall(std::string& topic, std::string& recall_handle, 
std::error_code& ec) noexcept {
-  return impl_->recall(topic, recall_handle, ec);
+  try {
+    return impl_->recall(topic, recall_handle, ec);
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in recall: {}", e.what());
+    return {};
+  }
 }
 
 ProducerBuilder Producer::newBuilder() {
@@ -78,7 +105,7 @@ ProducerBuilder Producer::newBuilder() {
 
 ProducerBuilder::ProducerBuilder() : impl_(std::make_shared<ProducerImpl>()){}
 
-ProducerBuilder& ProducerBuilder::withConfiguration(const Configuration& 
configuration) {
+ProducerBuilder& ProducerBuilder::withConfiguration(Configuration 
configuration) {
   auto name_server_resolver = 
std::make_shared<StaticNameServerResolver>(configuration.endpoints());
   impl_->withNameServerResolver(std::move(name_server_resolver));
   impl_->withResourceNamespace(configuration.resourceNamespace());
@@ -89,13 +116,13 @@ ProducerBuilder& ProducerBuilder::withConfiguration(const 
Configuration& configu
   return *this;
 }
 
-ProducerBuilder& ProducerBuilder::withTopics(const std::vector<std::string>& 
topics) {
-  impl_->withTopics(topics);
+ProducerBuilder& ProducerBuilder::withTopics(std::vector<std::string> topics) {
+  impl_->withTopics(std::move(topics));
   return *this;
 }
 
-ProducerBuilder& ProducerBuilder::withTransactionChecker(const 
TransactionChecker& checker) {
-  impl_->transaction_checker_ = checker;
+ProducerBuilder& ProducerBuilder::withTransactionChecker(TransactionChecker 
checker) {
+  impl_->transaction_checker_ = std::move(checker);
   return *this;
 }
 
diff --git a/cpp/source/rocketmq/ProducerImpl.cpp 
b/cpp/source/rocketmq/ProducerImpl.cpp
index 3450faf2..22027ab8 100644
--- a/cpp/source/rocketmq/ProducerImpl.cpp
+++ b/cpp/source/rocketmq/ProducerImpl.cpp
@@ -55,9 +55,9 @@ ProducerImpl::~ProducerImpl() {
 void ProducerImpl::start() {
   ClientImpl::start();
 
-  State expecting = State::STARTING;
-  if (!state_.compare_exchange_strong(expecting, State::STARTED)) {
-    SPDLOG_ERROR("Producer started with an unexpected state. Expecting: {}, 
Actual: {}", State::STARTING,
+  State expected = State::CREATED;
+  if (!state_.compare_exchange_strong(expected, State::STARTED)) {
+    SPDLOG_ERROR("Producer started with unexpected state. Expecting: {}, 
Actual: {}", State::CREATED,
                  state_.load(std::memory_order_relaxed));
     return;
   }
@@ -65,19 +65,19 @@ void ProducerImpl::start() {
   client_manager_->addClientObserver(shared_from_this());
 }
 
-void ProducerImpl::shutdown() {
+void ProducerImpl::shutdown() noexcept {
   State expected = State::STARTED;
-  if (!state_.compare_exchange_strong(expected, State::STOPPING)) {
-    SPDLOG_ERROR("Shutdown with unexpected state. Expecting: {}, Actual: {}", 
State::STOPPING,
-                 state_.load(std::memory_order_relaxed));
+  if (!state_.compare_exchange_strong(expected, State::STOPPED)) {
     return;
   }
 
-  notifyClientTermination();
+  {
+    absl::MutexLock lock(&topic_publish_info_mtx_);
+    topic_publish_info_table_.clear();
+  }
 
   ClientImpl::shutdown();
-  assert(State::STOPPED == state_.load());
-  SPDLOG_INFO("Producer instance stopped");
+  SPDLOG_INFO("ProducerImpl stopped");
 }
 
 void ProducerImpl::notifyClientTermination() {
@@ -138,14 +138,11 @@ void ProducerImpl::wrapSendMessageRequest(const Message& 
message, SendMessageReq
 
   // Delivery Timestamp
   if (message.deliveryTimestamp().time_since_epoch().count()) {
-    auto delivery_timestamp = message.deliveryTimestamp();
-    if (delivery_timestamp.time_since_epoch().count()) {
-      auto duration = delivery_timestamp.time_since_epoch();
-      
system_properties->set_delivery_attempt(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
-      auto mutable_delivery_timestamp = 
system_properties->mutable_delivery_timestamp();
-      
mutable_delivery_timestamp->set_seconds(std::chrono::duration_cast<std::chrono::seconds>(duration).count());
-      
mutable_delivery_timestamp->set_nanos(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()
 % 1000000000);
-    }
+    auto duration = message.deliveryTimestamp().time_since_epoch();
+    
system_properties->set_delivery_attempt(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
+    auto mutable_delivery_timestamp = 
system_properties->mutable_delivery_timestamp();
+    
mutable_delivery_timestamp->set_seconds(std::chrono::duration_cast<std::chrono::seconds>(duration).count());
+    
mutable_delivery_timestamp->set_nanos(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()
 % 1000000000);
   }
 
   // Born-time
@@ -233,16 +230,13 @@ SendReceipt ProducerImpl::send(MessageConstPtr message, 
std::error_code& ec) noe
 
   // Define callback
   auto callback =
-      [&, mtx, cv](const std::error_code& code, const SendReceipt& receipt) 
mutable {
+      [&, mtx, cv](const std::error_code& code, SendReceipt&& receipt) mutable 
{
     ec = code;
-    // SendReceipt contains a unique_ptr (MessageConstPtr) and is non-copyable.
-    // The receipt here is always a local temporary in SendContext, so 
const_cast + move is safe.
-    auto& mutable_receipt = const_cast<SendReceipt&>(receipt);
-    send_receipt.target = std::move(mutable_receipt.target);
-    send_receipt.message_id = std::move(mutable_receipt.message_id);
-    send_receipt.message = std::move(mutable_receipt.message);
-    send_receipt.transaction_id = std::move(mutable_receipt.transaction_id);
-    send_receipt.recall_handle = std::move(mutable_receipt.recall_handle);
+    send_receipt.target = std::move(receipt.target);
+    send_receipt.message_id = std::move(receipt.message_id);
+    send_receipt.message = std::move(receipt.message);
+    send_receipt.transaction_id = std::move(receipt.transaction_id);
+    send_receipt.recall_handle = std::move(receipt.recall_handle);
     {
       absl::MutexLock lk(mtx.get());
       completed = true;
@@ -268,7 +262,8 @@ void ProducerImpl::send(MessageConstPtr message, 
SendCallback cb) {
   if (ec) {
     SendReceipt send_receipt;
     send_receipt.message = std::move(message);
-    cb(ec, send_receipt);
+    cb(ec, std::move(send_receipt));
+    return;
   }
 
   std::string topic = message->topic();
@@ -282,7 +277,7 @@ void ProducerImpl::send(MessageConstPtr message, 
SendCallback cb) {
     if (ec) {
       SendReceipt send_receipt;
       send_receipt.message = std::move(ptr);
-      cb(ec, send_receipt);
+      cb(ec, std::move(send_receipt));
       return;
     }
 
@@ -290,7 +285,7 @@ void ProducerImpl::send(MessageConstPtr message, 
SendCallback cb) {
       std::error_code ec = ErrorCode::NotFound;
       SendReceipt     send_receipt;
       send_receipt.message = std::move(ptr);
-      cb(ec, send_receipt);
+      cb(ec, std::move(send_receipt));
       return;
     }
 
@@ -300,7 +295,7 @@ void ProducerImpl::send(MessageConstPtr message, 
SendCallback cb) {
       std::error_code ec = ErrorCode::NotFound;
       SendReceipt     send_receipt;
       send_receipt.message = std::move(ptr);
-      cb(ec, send_receipt);
+      cb(ec, std::move(send_receipt));
       return;
     }
 
@@ -375,14 +370,14 @@ void ProducerImpl::send0(MessageConstPtr message, const 
SendCallback& callback,
   validate(*message, ec);
   if (ec) {
     send_receipt.message = std::move(message);
-    callback(ec, send_receipt);
+    callback(ec, std::move(send_receipt));
     return;
   }
 
   if (list.empty()) {
     ec = ErrorCode::NotFound;
     send_receipt.message = std::move(message);
-    callback(ec, send_receipt);
+    callback(ec, std::move(send_receipt));
     return;
   }
 
@@ -682,11 +677,11 @@ void 
ProducerImpl::topicsOfInterest(std::vector<std::string> &topics) {
   }
 }
 
-void ProducerImpl::withTopics(const std::vector<std::string> &topics) {
+void ProducerImpl::withTopics(std::vector<std::string> topics) {
   absl::MutexLock lk(&topics_mtx_);
-  for (auto &topic: topics) {
+  for (auto& topic : topics) {
     if (std::find(topics_.begin(), topics_.end(), topic) == topics_.end()) {
-      topics_.push_back(topic);
+      topics_.push_back(std::move(topic));
     }
   }
 }
diff --git a/cpp/source/rocketmq/PushConsumer.cpp 
b/cpp/source/rocketmq/PushConsumer.cpp
index a6dd3d35..726ee673 100644
--- a/cpp/source/rocketmq/PushConsumer.cpp
+++ b/cpp/source/rocketmq/PushConsumer.cpp
@@ -17,18 +17,28 @@
 #include <chrono>
 #include <memory>
 
+#include <spdlog/spdlog.h>
+
 #include "PushConsumerImpl.h"
 #include "StaticNameServerResolver.h"
 #include "rocketmq/PushConsumer.h"
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-void PushConsumer::subscribe(std::string topic, FilterExpression 
filter_expression) {
-  impl_->subscribe(std::move(topic), filter_expression.content_, 
filter_expression.type_);
+void PushConsumer::subscribe(std::string topic, FilterExpression 
filter_expression) noexcept {
+  try {
+    impl_->subscribe(std::move(topic), filter_expression.content_, 
filter_expression.type_);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in subscribe: {}", e.what());
+  }
 }
 
-void PushConsumer::unsubscribe(const std::string& topic) {
-  impl_->unsubscribe(topic);
+void PushConsumer::unsubscribe(const std::string& topic) noexcept {
+  try {
+    impl_->unsubscribe(topic);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in unsubscribe: {}", e.what());
+  }
 }
 
 PushConsumerBuilder PushConsumer::newBuilder() {
diff --git a/cpp/source/rocketmq/PushConsumerImpl.cpp 
b/cpp/source/rocketmq/PushConsumerImpl.cpp
index bd2a9977..124db9f8 100644
--- a/cpp/source/rocketmq/PushConsumerImpl.cpp
+++ b/cpp/source/rocketmq/PushConsumerImpl.cpp
@@ -54,20 +54,20 @@ void 
PushConsumerImpl::topicsOfInterest(std::vector<std::string> &topics) {
 }
 
 void PushConsumerImpl::start() {
+  if (!message_listener_) {
+    SPDLOG_ERROR("Required message listener is missing");
+    abort();
+  }
+
   ClientImpl::start();
 
-  State expecting = State::STARTING;
-  if (!state_.compare_exchange_strong(expecting, State::STARTED)) {
-    SPDLOG_ERROR("Unexpected consumer state. Expecting: {}, Actual: {}", 
State::STARTING,
+  State expected = State::CREATED;
+  if (!state_.compare_exchange_strong(expected, State::STARTED)) {
+    SPDLOG_ERROR("PushConsumer started with unexpected state. Expecting: {}, 
Actual: {}", State::CREATED,
                  state_.load(std::memory_order_relaxed));
     return;
   }
 
-  if (!message_listener_) {
-    SPDLOG_ERROR("Required message listener is missing");
-    abort();
-  }
-
   client_config_.subscriber.group.set_resource_namespace(resourceNamespace());
   client_manager_->addClientObserver(shared_from_this());
 
@@ -109,36 +109,33 @@ void PushConsumerImpl::start() {
 const char* PushConsumerImpl::SCAN_ASSIGNMENT_TASK_NAME = 
"scan-assignment-task";
 const char* PushConsumerImpl::COLLECT_STATS_TASK_NAME = "collect-stats-task";
 
-void PushConsumerImpl::shutdown() {
-  State expecting = State::STARTED;
-  if (state_.compare_exchange_strong(expecting, State::STOPPING)) {
-    if (scan_assignment_handle_) {
-      client_manager_->getScheduler()->cancel(scan_assignment_handle_);
-      SPDLOG_DEBUG("Scan assignment periodic task cancelled");
-    }
-
-    if (collect_stats_handle_) {
-      client_manager_->getScheduler()->cancel(collect_stats_handle_);
-      SPDLOG_DEBUG("Collect cache stats periodic task cancelled");
-    }
+void PushConsumerImpl::shutdown() noexcept {
+  State expected = State::STARTED;
+  if (!state_.compare_exchange_strong(expected, State::STOPPED)) {
+    return;
+  }
 
-    {
-      absl::MutexLock lock(&process_queue_table_mtx_);
-      process_queue_table_.clear();
-    }
+  if (scan_assignment_handle_) {
+    client_manager_->getScheduler()->cancel(scan_assignment_handle_);
+    SPDLOG_DEBUG("Scan assignment periodic task cancelled");
+  }
 
-    if (consume_message_service_) {
-      consume_message_service_->shutdown();
-    }
+  if (collect_stats_handle_) {
+    client_manager_->getScheduler()->cancel(collect_stats_handle_);
+    SPDLOG_DEBUG("Collect cache stats periodic task cancelled");
+  }
 
-    // Shutdown services started by parent
-    ClientImpl::shutdown();
+  {
+    absl::MutexLock lock(&process_queue_table_mtx_);
+    process_queue_table_.clear();
+  }
 
-    SPDLOG_INFO("PushConsumerImpl stopped");
-  } else {
-    SPDLOG_ERROR("Shutdown with unexpected state. Expecting: {}, Actual: {}", 
State::STARTED,
-                 state_.load(std::memory_order_relaxed));
+  if (consume_message_service_) {
+    consume_message_service_->shutdown();
   }
+
+  ClientImpl::shutdown();
+  SPDLOG_INFO("PushConsumerImpl stopped");
 }
 
 void PushConsumerImpl::subscribe(const std::string& topic, const std::string& 
expression,
diff --git a/cpp/source/rocketmq/SendContext.cpp 
b/cpp/source/rocketmq/SendContext.cpp
index 5f6b8fdc..17b8c4b5 100644
--- a/cpp/source/rocketmq/SendContext.cpp
+++ b/cpp/source/rocketmq/SendContext.cpp
@@ -59,7 +59,7 @@ void SendContext::onSuccess(const SendResult& send_result) 
noexcept {
   send_receipt.transaction_id = send_result.transaction_id;
   send_receipt.recall_handle = send_result.recall_handle;
   send_receipt.message = std::move(message_);
-  callback_(send_result.ec, send_receipt);
+  callback_(send_result.ec, std::move(send_receipt));
 }
 
 void SendContext::onFailure(const std::error_code& ec) noexcept {
@@ -90,7 +90,7 @@ void SendContext::onFailure(const std::error_code& ec) 
noexcept {
     SPDLOG_WARN("Retried {} times, which exceeds the limit: {}", 
attempt_times_, producer->maxAttemptTimes());
     SendReceipt receipt{};
     receipt.message = std::move(message_);
-    callback_(ec, receipt);
+    callback_(ec, std::move(receipt));
     return;
   }
 
@@ -98,10 +98,19 @@ void SendContext::onFailure(const std::error_code& ec) 
noexcept {
     SPDLOG_WARN("No alternative hosts to perform additional retries");
     SendReceipt receipt{};
     receipt.message = std::move(message_);
-    callback_(ec, receipt);
+    callback_(ec, std::move(receipt));
     return;
   }
 
+  // Isolate the failed endpoint to avoid retrying on the same broken broker
+  {
+    auto& failed_queue = candidates_[(attempt_times_ - 1) % 
candidates_.size()];
+    auto target = failed_queue.broker().endpoints().addresses(0).host() + ":" +
+                  
std::to_string(failed_queue.broker().endpoints().addresses(0).port());
+    producer->isolateEndpoint(target);
+    SPDLOG_INFO("Isolated endpoint {} after send failure", target);
+  }
+
   auto message_queue = candidates_[attempt_times_ % candidates_.size()];
   request_time_ = std::chrono::steady_clock::now();
 
diff --git a/cpp/source/rocketmq/SimpleConsumer.cpp 
b/cpp/source/rocketmq/SimpleConsumer.cpp
index 152c0537..a791e869 100644
--- a/cpp/source/rocketmq/SimpleConsumer.cpp
+++ b/cpp/source/rocketmq/SimpleConsumer.cpp
@@ -17,6 +17,8 @@
 
 #include "rocketmq/SimpleConsumer.h"
 
+#include <spdlog/spdlog.h>
+
 #include "SimpleConsumerImpl.h"
 #include "StaticNameServerResolver.h"
 #include "rocketmq/ErrorCode.h"
@@ -37,92 +39,135 @@ void SimpleConsumer::start() {
   impl_->start();
 }
 
-void SimpleConsumer::subscribe(std::string topic, FilterExpression 
filter_expression) {
-  impl_->subscribe(topic, filter_expression);
+void SimpleConsumer::subscribe(std::string topic, FilterExpression 
filter_expression) noexcept {
+  try {
+    impl_->subscribe(topic, filter_expression);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in subscribe: {}", e.what());
+  }
 }
 
-void SimpleConsumer::unsubscribe(const std::string& topic) {
-  impl_->unsubscribe(topic);
+void SimpleConsumer::unsubscribe(const std::string& topic) noexcept {
+  try {
+    impl_->unsubscribe(topic);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in unsubscribe: {}", e.what());
+  }
 }
 
 void SimpleConsumer::receive(std::size_t limit,
                              std::chrono::milliseconds invisible_duration,
                              std::error_code& ec,
-                             std::vector<MessageConstSharedPtr>& messages) {
-  auto mtx = std::make_shared<absl::Mutex>();
-  auto cv = std::make_shared<absl::CondVar>();
-  bool completed = false;
-  auto callback = [&, mtx, cv](const std::error_code& code, const 
std::vector<MessageConstSharedPtr>& result) {
-    {
-      absl::MutexLock lk(mtx.get());
-      if (code && code != ErrorCode::NoContent) {
-        ec = code;
-        SPDLOG_WARN("Failed to receive message. Cause: {}", code.message());
+                             std::vector<MessageConstSharedPtr>& messages) 
noexcept {
+  try {
+    auto mtx = std::make_shared<absl::Mutex>();
+    auto cv = std::make_shared<absl::CondVar>();
+    bool completed = false;
+    auto callback = [&, mtx, cv](const std::error_code& code, const 
std::vector<MessageConstSharedPtr>& result) {
+      {
+        absl::MutexLock lk(mtx.get());
+        if (code && code != ErrorCode::NoContent) {
+          ec = code;
+          SPDLOG_WARN("Failed to receive message. Cause: {}", code.message());
+        }
+        completed = true;
+        messages.insert(messages.end(), result.begin(), result.end());
       }
-      completed = true;
-      messages.insert(messages.end(), result.begin(), result.end());
-    }
-    cv->SignalAll();
-  };
+      cv->SignalAll();
+    };
 
-  impl_->receive(limit, invisible_duration, callback);
+    impl_->receive(limit, invisible_duration, callback);
 
-  {
-    absl::MutexLock lk(mtx.get());
-    while (!completed) {
-      cv->Wait(mtx.get());
+    {
+      absl::MutexLock lk(mtx.get());
+      while (!completed) {
+        cv->Wait(mtx.get());
+      }
     }
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in receive: {}", e.what());
   }
 }
 
 void SimpleConsumer::asyncReceive(std::size_t limit,
                                   std::chrono::milliseconds invisible_duration,
-                                  ReceiveCallback callback) {
-  impl_->receive(limit, invisible_duration, callback);
+                                  ReceiveCallback callback) noexcept {
+  try {
+    impl_->receive(limit, invisible_duration, callback);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in asyncReceive: {}", e.what());
+    std::error_code ec = std::make_error_code(std::errc::io_error);
+    std::vector<MessageConstSharedPtr> empty;
+    callback(ec, empty);
+  }
 }
 
-void SimpleConsumer::ack(const Message& message, std::error_code& ec) {
-  impl_->ack(message, ec);
+void SimpleConsumer::ack(const Message& message, std::error_code& ec) noexcept 
{
+  try {
+    impl_->ack(message, ec);
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in ack: {}", e.what());
+  }
 }
 
-void SimpleConsumer::asyncAck(const Message& message, AckCallback callback) {
-  impl_->ackAsync(message, callback);
+void SimpleConsumer::asyncAck(const Message& message, AckCallback callback) 
noexcept {
+  try {
+    impl_->ackAsync(message, callback);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in asyncAck: {}", e.what());
+    std::error_code ec = std::make_error_code(std::errc::io_error);
+    callback(ec);
+  }
 }
 
 void SimpleConsumer::changeInvisibleDuration(const Message& message, 
std::string& receipt_handle,
                                              std::chrono::milliseconds 
duration,
-                                             std::error_code& ec) {
-  auto mtx = std::make_shared<absl::Mutex>();
-  auto cv = std::make_shared<absl::CondVar>();
-  bool completed = false;
+                                             std::error_code& ec) noexcept {
+  try {
+    auto mtx = std::make_shared<absl::Mutex>();
+    auto cv = std::make_shared<absl::CondVar>();
+    bool completed = false;
+
+    auto callback =
+        [&, mtx, cv](const std::error_code& code, std::string& 
server_receipt_handle) {
+      {
+        absl::MutexLock lk(mtx.get());
+        completed = true;
+        ec = code;
+        if (!ec) {
+          receipt_handle = server_receipt_handle;
+        }
+      }
+      cv->Signal();
+    };
+
+    impl_->changeInvisibleDuration(message, receipt_handle, duration, 
callback);
 
-  auto callback =
-      [&, mtx, cv](const std::error_code& code, std::string& 
server_receipt_handle) {
     {
       absl::MutexLock lk(mtx.get());
-      completed = true;
-      ec = code;
-      if (!ec) {
-        receipt_handle = server_receipt_handle;
+      if (!completed) {
+        cv->Wait(mtx.get());
       }
     }
-    cv->Signal();
-  };
-
-  impl_->changeInvisibleDuration(message, receipt_handle, duration, callback);
-
-  {
-    absl::MutexLock lk(mtx.get());
-    if (!completed) {
-      cv->Wait(mtx.get());
-    }
+  } catch (const std::exception& e) {
+    ec = std::make_error_code(std::errc::io_error);
+    SPDLOG_ERROR("Exception in changeInvisibleDuration: {}", e.what());
   }
 }
 
 void SimpleConsumer::asyncChangeInvisibleDuration(const Message& message, 
std::string& receipt_handle,
                                                   std::chrono::milliseconds 
duration,
-                                                  
ChangeInvisibleDurationCallback callback) {
-  impl_->changeInvisibleDuration(message, receipt_handle, duration, callback);
+                                                  
ChangeInvisibleDurationCallback callback) noexcept {
+  try {
+    impl_->changeInvisibleDuration(message, receipt_handle, duration, 
callback);
+  } catch (const std::exception& e) {
+    SPDLOG_ERROR("Exception in asyncChangeInvisibleDuration: {}", e.what());
+    std::error_code ec = std::make_error_code(std::errc::io_error);
+    std::string handle;
+    callback(ec, handle);
+  }
 }
 
 SimpleConsumer SimpleConsumerBuilder::build() {
diff --git a/cpp/source/rocketmq/SimpleConsumerImpl.cpp 
b/cpp/source/rocketmq/SimpleConsumerImpl.cpp
index d1508d8b..96bb7b37 100644
--- a/cpp/source/rocketmq/SimpleConsumerImpl.cpp
+++ b/cpp/source/rocketmq/SimpleConsumerImpl.cpp
@@ -76,43 +76,44 @@ void 
SimpleConsumerImpl::topicsOfInterest(std::vector<std::string> &topics) {
   }
 }
 
-/**
- * @brief Start SimpleConsumer
- *
- * During start, we need synchronously fetch routes and query assignments
- */
 void SimpleConsumerImpl::start() {
   ClientImpl::start();
-  State expected = State::STARTING;
-  if (state_.compare_exchange_strong(expected, State::STARTED, 
std::memory_order_relaxed)) {
-    
client_config_.subscriber.group.set_resource_namespace(resourceNamespace());
-    refreshAssignments();
-
-    std::weak_ptr<SimpleConsumerImpl> consumer(shared_from_this());
-    auto refresh_assignment_task = [consumer]() {
-      auto simple_consumer = consumer.lock();
-      if (simple_consumer) {
-        simple_consumer->refreshAssignments0();
-      }
-    };
-
-    // refer java sdk: set refresh interval to 5 seconds
-    // org.apache.rocketmq.client.java.impl.ClientImpl#startUp
-    refresh_assignment_task_ = manager()->getScheduler()->schedule(
-        refresh_assignment_task, "RefreshAssignmentTask",
-        std::chrono::seconds(5), std::chrono::seconds(5));
 
-    client_manager_->addClientObserver(shared_from_this());
+  State expected = State::CREATED;
+  if (!state_.compare_exchange_strong(expected, State::STARTED)) {
+    SPDLOG_ERROR("SimpleConsumer started with unexpected state. Expecting: {}, 
Actual: {}", State::CREATED,
+                 state_.load(std::memory_order_relaxed));
+    return;
   }
+
+  client_config_.subscriber.group.set_resource_namespace(resourceNamespace());
+  refreshAssignments();
+
+  std::weak_ptr<SimpleConsumerImpl> consumer(shared_from_this());
+  auto refresh_assignment_task = [consumer]() {
+    auto simple_consumer = consumer.lock();
+    if (simple_consumer) {
+      simple_consumer->refreshAssignments0();
+    }
+  };
+
+  // refer java sdk: set refresh interval to 5 seconds
+  // org.apache.rocketmq.client.java.impl.ClientImpl#startUp
+  refresh_assignment_task_ = manager()->getScheduler()->schedule(
+      refresh_assignment_task, "RefreshAssignmentTask",
+      std::chrono::seconds(5), std::chrono::seconds(5));
+
+  client_manager_->addClientObserver(shared_from_this());
 }
 
-void SimpleConsumerImpl::shutdown() {
+void SimpleConsumerImpl::shutdown() noexcept {
   State expected = State::STARTED;
-
-  if (state_.compare_exchange_strong(expected, State::STOPPING, 
std::memory_order_relaxed)) {
-    manager()->getScheduler()->cancel(refresh_assignment_task_);
-    ClientImpl::shutdown();
+  if (!state_.compare_exchange_strong(expected, State::STOPPED)) {
+    return;
   }
+
+  manager()->getScheduler()->cancel(refresh_assignment_task_);
+  ClientImpl::shutdown();
 }
 
 void SimpleConsumerImpl::subscribe(std::string topic, FilterExpression 
expression) {
@@ -245,6 +246,10 @@ void SimpleConsumerImpl::refreshAssignment(const 
std::string& topic, std::functi
   std::weak_ptr<SimpleConsumerImpl> consumer(shared_from_this());
   auto callback = [consumer, topic, cb](const std::error_code& ec, const 
rmq::QueryAssignmentResponse& response) {
     auto simple_consumer = consumer.lock();
+    if (!simple_consumer) {
+      SPDLOG_WARN("SimpleConsumer has been destructed, dropping assignment 
response");
+      return;
+    }
     const auto& assignments = response.assignments();
     if (assignments.empty()) {
       cb(ec);
@@ -441,7 +446,7 @@ void SimpleConsumerImpl::ackAsync(const Message& message, 
AckCallback callback)
 
 void SimpleConsumerImpl::changeInvisibleDuration(const Message& message, 
std::string& receipt_handle,
                                                  std::chrono::milliseconds 
duration,
-                                                 const 
ChangeInvisibleDurationCallback callback) {
+                                                 
ChangeInvisibleDurationCallback callback) {
   Metadata metadata;
   Signature::sign(client_config_, metadata);
 
diff --git a/cpp/source/rocketmq/include/ClientImpl.h 
b/cpp/source/rocketmq/include/ClientImpl.h
index 01967384..563d5089 100644
--- a/cpp/source/rocketmq/include/ClientImpl.h
+++ b/cpp/source/rocketmq/include/ClientImpl.h
@@ -55,7 +55,8 @@ public:
 
   virtual void start();
 
-  virtual void shutdown();
+  /// Must be noexcept — called from destructors (Bug 21 fix).
+  virtual void shutdown() noexcept;
 
   void getRouteFor(const std::string& topic, const std::function<void(const 
std::error_code&, TopicRouteDataPtr)>& cb)
       LOCKS_EXCLUDED(inflight_route_requests_mtx_, topic_route_table_mtx_);
@@ -71,8 +72,7 @@ public:
   void heartbeat() override;
 
   bool active() override {
-    State state = state_.load(std::memory_order_relaxed);
-    return State::STARTING == state || State::STARTED == state;
+    return State::STARTED == state_.load(std::memory_order_relaxed);
   }
 
   void onRemoteEndpointRemoval(const std::vector<std::string>& hosts) override 
LOCKS_EXCLUDED(isolated_endpoints_mtx_);
@@ -152,6 +152,7 @@ protected:
   ClientManagerPtr client_manager_;
 
   std::atomic<State> state_;
+  std::atomic<bool> shutdown_requested_{false};
 
   absl::flat_hash_map<std::string, TopicRouteDataPtr> topic_route_table_ 
GUARDED_BY(topic_route_table_mtx_);
   absl::Mutex topic_route_table_mtx_ 
ACQUIRED_AFTER(inflight_route_requests_mtx_); // protects topic_route_table_
diff --git a/cpp/source/rocketmq/include/FifoProducerPartition.h 
b/cpp/source/rocketmq/include/FifoProducerPartition.h
index 8d0e00d8..199d11f4 100644
--- a/cpp/source/rocketmq/include/FifoProducerPartition.h
+++ b/cpp/source/rocketmq/include/FifoProducerPartition.h
@@ -42,7 +42,7 @@ public:
 
   void trySend() LOCKS_EXCLUDED(messages_mtx_);
 
-  void onComplete(const std::error_code& ec, const SendReceipt& receipt, 
SendCallback& callback);
+  void onComplete(const std::error_code& ec, SendReceipt&& receipt, 
SendCallback& callback);
 
 private:
   std::shared_ptr<ProducerImpl> producer_;
diff --git a/cpp/source/rocketmq/include/ProducerImpl.h 
b/cpp/source/rocketmq/include/ProducerImpl.h
index 237c242e..53300ad8 100644
--- a/cpp/source/rocketmq/include/ProducerImpl.h
+++ b/cpp/source/rocketmq/include/ProducerImpl.h
@@ -47,7 +47,7 @@ public:
 
   void start() override;
 
-  void shutdown() override;
+  void shutdown() noexcept override;
 
   /**
    * Note we require application to transfer ownership of the message
@@ -128,7 +128,7 @@ public:
 
   void topicsOfInterest(std::vector<std::string> &topics) override 
LOCKS_EXCLUDED(topics_mtx_);
 
-  void withTopics(const std::vector<std::string> &topics) 
LOCKS_EXCLUDED(topics_mtx_);
+  void withTopics(std::vector<std::string> topics) LOCKS_EXCLUDED(topics_mtx_);
 
   const PublishStats& stats() const {
     return stats_;
diff --git a/cpp/source/rocketmq/include/PushConsumerImpl.h 
b/cpp/source/rocketmq/include/PushConsumerImpl.h
index 42c3fe49..e8a80838 100644
--- a/cpp/source/rocketmq/include/PushConsumerImpl.h
+++ b/cpp/source/rocketmq/include/PushConsumerImpl.h
@@ -56,7 +56,7 @@ public:
 
   void start() override;
 
-  void shutdown() override;
+  void shutdown() noexcept override;
 
   void subscribe(const std::string& topic,
                  const std::string& expression,
diff --git a/cpp/source/rocketmq/include/SimpleConsumerImpl.h 
b/cpp/source/rocketmq/include/SimpleConsumerImpl.h
index 22850043..e0dc205f 100644
--- a/cpp/source/rocketmq/include/SimpleConsumerImpl.h
+++ b/cpp/source/rocketmq/include/SimpleConsumerImpl.h
@@ -22,7 +22,6 @@
 #include "rocketmq/FilterExpression.h"
 #include "rocketmq/SimpleConsumer.h"
 
-using namespace std::chrono;
 ROCKETMQ_NAMESPACE_BEGIN
 
 class SimpleConsumerImpl : virtual public ClientImpl, public 
std::enable_shared_from_this<SimpleConsumerImpl> {
@@ -41,7 +40,7 @@ public:
 
   void start() override;
 
-  void shutdown() override;
+  void shutdown() noexcept override;
 
   void subscribe(std::string topic, FilterExpression expression) 
LOCKS_EXCLUDED(subscriptions_mtx_);
 
@@ -55,7 +54,7 @@ public:
 
   void changeInvisibleDuration(const Message& message, std::string& 
receipt_handle,
                                std::chrono::milliseconds duration,
-                               const ChangeInvisibleDurationCallback callback);
+                               ChangeInvisibleDurationCallback callback);
 
   void withReceiveMessageTimeout(std::chrono::milliseconds receive_timeout) {
     long_polling_duration_ = receive_timeout;

Reply via email to