Improve Error Handling (#23)

Improve error handling
diff --git a/.clang-format b/.clang-format
index bb3e5d7..6345fe1 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,6 +7,12 @@
 SortIncludes: true
 IndentWidth: 2
 NamespaceIndentation: Inner
+AlwaysBreakTemplateDeclarations: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+IndentCaseBlocks: false
+IndentCaseLabels: true
 ...
 
 ---
diff --git a/api/BUILD.bazel b/api/BUILD.bazel
index d971e6d..8693427 100644
--- a/api/BUILD.bazel
+++ b/api/BUILD.bazel
@@ -5,4 +5,7 @@
     hdrs = glob(["rocketmq/*.h"]),
     strip_include_prefix = "//api",
     visibility = ["//visibility:public"],
+    deps = [
+        "@com_google_absl//absl/strings",
+    ],
 )
\ No newline at end of file
diff --git a/api/rocketmq/AsyncCallback.h b/api/rocketmq/AsyncCallback.h
index bb431a1..f7d5ae7 100644
--- a/api/rocketmq/AsyncCallback.h
+++ b/api/rocketmq/AsyncCallback.h
@@ -1,35 +1,34 @@
 #pragma once
 
+#include <system_error>
+
 #include "MQClientException.h"
 #include "PullResult.h"
 #include "SendResult.h"
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-struct AsyncCallback {};
-
-enum class SendCallbackType : int8_t { noAutoDeleteSendCallback = 0, autoDeleteSendCallback = 1 };
-
-using sendCallbackType = SendCallbackType;
+class AsyncCallback {
+public:
+  virtual ~AsyncCallback() = default;
+};
 
 class SendCallback : public AsyncCallback {
 public:
-  virtual ~SendCallback() = default;
+  ~SendCallback() override = default;
 
-  virtual void onSuccess(SendResult& send_result) = 0;
+  virtual void onSuccess(SendResult &send_result) noexcept = 0;
 
-  virtual void onException(const MQException& e) = 0;
-
-  virtual SendCallbackType getSendCallbackType() { return SendCallbackType::noAutoDeleteSendCallback; }
+  virtual void onFailure(const std::error_code &ec) noexcept = 0;
 };
 
 class PullCallback : public AsyncCallback {
 public:
-  virtual ~PullCallback() = default;
+  ~PullCallback() override = default;
 
-  virtual void onSuccess(const PullResult& pull_result) = 0;
+  virtual void onSuccess(const PullResult &pull_result) noexcept = 0;
 
-  virtual void onException(const MQException& e) = 0;
+  virtual void onFailure(const std::error_code &ec) noexcept = 0;
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/api/rocketmq/DefaultMQProducer.h b/api/rocketmq/DefaultMQProducer.h
index 94532d1..4110fd2 100644
--- a/api/rocketmq/DefaultMQProducer.h
+++ b/api/rocketmq/DefaultMQProducer.h
@@ -2,10 +2,12 @@
 
 #include <chrono>
 #include <memory>
+#include <system_error>
 #include <vector>
 
 #include "AsyncCallback.h"
 #include "CredentialsProvider.h"
+#include "ErrorCode.h"
 #include "LocalTransactionStateChecker.h"
 #include "Logger.h"
 #include "MQMessage.h"
@@ -42,18 +44,31 @@
    */
   void setSendMsgTimeout(std::chrono::milliseconds timeout);
 
-  SendResult send(const MQMessage& message, const std::string& message_group);
+  SendResult send(MQMessage& message, const std::string& message_group);
 
   /**
    * Send message in synchronous manner.
    * @param message  Message to send.
    * @param filter_active_broker Do NOT rely on this parameter. it has been deprecated.
    */
-  SendResult send(const MQMessage& message, bool filter_active_broker = false);
-  SendResult send(const MQMessage& message, const MQMessageQueue& message_queue);
-  SendResult send(const MQMessage& message, MessageQueueSelector* selector, void* arg);
+  SendResult send(const MQMessage& message, bool filter_active_broker = true);
 
-  SendResult send(const MQMessage& message, MessageQueueSelector* selector, void* arg, int retry_times,
+  SendResult send(const MQMessage& message, std::error_code& ec) noexcept;
+
+  SendResult send(MQMessage& message, const MQMessageQueue& message_queue);
+  SendResult send(MQMessage& message, MessageQueueSelector* selector, void* arg);
+
+  /**
+   * @brief This function is deprecated.
+   *
+   * @param message
+   * @param selector
+   * @param arg
+   * @param retry_times retry_times is ignored with respect to member retry_times setting.
+   * @param select_active_broker
+   * @return SendResult
+   */
+  SendResult send(MQMessage& message, MessageQueueSelector* selector, void* arg, int retry_times,
                   bool select_active_broker = false);
 
   /**
@@ -63,8 +78,8 @@
    * @param select_active_broker Do NOT rely on this parameter. it has been deprecated.
    */
   void send(const MQMessage& message, SendCallback* send_callback, bool select_active_broker = false);
-  void send(const MQMessage& message, const MQMessageQueue& message_queue, SendCallback* send_callback);
-  void send(const MQMessage& message, MessageQueueSelector* selector, void* arg, SendCallback* send_callback);
+  void send(MQMessage& message, const MQMessageQueue& message_queue, SendCallback* send_callback);
+  void send(MQMessage& message, MessageQueueSelector* selector, void* arg, SendCallback* send_callback);
 
   /**
    * send message in Oneway(The implementation is simply ignore the result of send message in synchronous).
@@ -72,8 +87,8 @@
    * @param select_active_broker Do NOT rely on this parameter. it has been deprecated.
    */
   void sendOneway(const MQMessage& message, bool select_active_broker = false);
-  void sendOneway(const MQMessage& message, const MQMessageQueue& message_queue);
-  void sendOneway(const MQMessage& message, MessageQueueSelector* selector, void* arg);
+  void sendOneway(MQMessage& message, const MQMessageQueue& message_queue);
+  void sendOneway(MQMessage& message, MessageQueueSelector* selector, void* arg);
 
   void setLocalTransactionStateChecker(LocalTransactionStateCheckerPtr checker);
 
diff --git a/api/rocketmq/ErrorCategory.h b/api/rocketmq/ErrorCategory.h
new file mode 100644
index 0000000..316db57
--- /dev/null
+++ b/api/rocketmq/ErrorCategory.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "ErrorCode.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+class ErrorCategory : public std::error_category {
+public:
+  static const ErrorCategory& instance() {
+    static ErrorCategory instance;
+    return instance;
+  }
+
+  const char* name() const noexcept override { return "RocketMQ"; }
+
+  std::string message(int code) const override;
+
+private:
+  ErrorCategory() = default;
+};
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/api/rocketmq/ErrorCode.h b/api/rocketmq/ErrorCode.h
index fa6cdf0..0e09dd6 100644
--- a/api/rocketmq/ErrorCode.h
+++ b/api/rocketmq/ErrorCode.h
@@ -2,26 +2,152 @@
 
 #include "RocketMQ.h"
 
+#include <cstdint>
+#include <system_error>
+#include <type_traits>
+
 ROCKETMQ_NAMESPACE_BEGIN
 
-constexpr int NO_TOPIC_ROUTE_INFO = -1;
+enum class ErrorCode : int {
+  Success = 0,
 
-constexpr int FAILED_TO_SELECT_MESSAGE_QUEUE = -2;
+  /**
+   * @brief Client state not as expected. Call Producer#start() first.
+   *
+   */
+  IllegalState = 1,
 
-constexpr int FAILED_TO_RESOLVE_BROKER_ADDRESS_FROM_TOPIC_ROUTE = -3;
+  /**
+   * @brief Bad configuration. For example, negative max-attempt-times.
+   *
+   */
+  BadConfiguration = 300,
 
-constexpr int FAILED_TO_SEND_MESSAGE = -4;
+  /**
+   * @brief The server cannot process the request due to apprent client-side
+   * error. For example, topic contains invalid character or is excessively
+   * long.
+   *
+   */
+  BadRequest = 400,
 
-constexpr int FAILED_TO_POP_MESSAGE_ASYNCHRONOUSLY = -5;
+  /**
+   * @brief Authentication failed. Possibly caused by invalid credentials.
+   *
+   */
+  Unauthorized = 401,
 
-constexpr int ILLEGAL_STATE = -6;
+  /**
+   * @brief Credentials are understood by server but authenticated user does not
+   * have privilege to perform the requested action.
+   *
+   */
+  Forbidden = 403,
 
-constexpr int MESSAGE_ILLEGAL = -7;
+  /**
+   * @brief Topic not found, which should be created through console or
+   * administration API before hand.
+   *
+   */
+  NotFound = 404,
 
-constexpr int BAD_CONFIGURATION = -8;
+  /**
+   * @brief Timeout when connecting, reading from or writing to brokers.
+   *
+   */
+  RequestTimeout = 408,
 
-constexpr int MESSAGE_QUEUE_ILLEGAL = -9;
+  /**
+   * @brief Message body is too large.
+   *
+   */
+  PayloadTooLarge = 413,
 
-constexpr int ERR_INVALID_MAX_ATTEMPT_TIME = -10;
+  /**
+   * @brief When trying to perform an action whose dependent procedure state is
+   * not right, this code will be used.
+   * 1. Acknowledge a message that is not previously received;
+   * 2. Commit/Rollback a transactional message that does not exist;
+   * 3. Commit an offset which is greater than maximum of partition;
+   */
+  PreconditionRequired = 428,
 
-ROCKETMQ_NAMESPACE_END
\ No newline at end of file
+  /**
+   * @brief Quota exchausted. The user has sent too many requests in a given
+   * amount of time.
+   *
+   */
+  TooManyRequest = 429,
+
+  /**
+   * @brief The server is unwilling to process the request because either an
+   * individual header field, or all the header fields collectively, are too
+   * large
+   *
+   */
+  HeaderFieldsTooLarge = 431,
+
+  /**
+   * @brief A server operator has received a legal demand to deny access to a
+   * resource or to a set of resources that includes the requested resource.
+   *
+   */
+  UnavailableForLegalReasons = 451,
+
+  /**
+   * @brief Server side interval error
+   *
+   */
+  InternalServerError = 500,
+
+  /**
+   * @brief The server either does not recognize the request method, or it lacks
+   * the ability to fulfil the request.
+   *
+   */
+  NotImplemented = 501,
+
+  /**
+   * @brief The server was acting as a gateway or proxy and received an invalid
+   * response from the upstream server.
+   *
+   */
+  BadGateway = 502,
+
+  /**
+   * @brief The server cannot handle the request (because it is overloaded or
+   * down for maintenance). Generally, this is a temporary state.
+   *
+   */
+  ServiceUnavailable = 503,
+
+  /**
+   * @brief The server was acting as a gateway or proxy and did not receive a
+   * timely response from the upstream server.
+   *
+   */
+  GatewayTimeout = 504,
+
+  /**
+   * @brief The server does not support the protocol version used in the
+   * request.
+   *
+   */
+  ProtocolVersionNotSupported = 505,
+
+  /**
+   * @brief The server is unable to store the representation needed to complete
+   * the request.
+   *
+   */
+  InsufficientStorage = 507,
+};
+
+std::error_code make_error_code(ErrorCode code);
+
+ROCKETMQ_NAMESPACE_END
+
+namespace std {
+template <>
+struct is_error_code_enum<ROCKETMQ_NAMESPACE::ErrorCode> : true_type {};
+} // namespace std
\ No newline at end of file
diff --git a/api/rocketmq/MQMessage.h b/api/rocketmq/MQMessage.h
index 23f4169..30dac59 100644
--- a/api/rocketmq/MQMessage.h
+++ b/api/rocketmq/MQMessage.h
@@ -7,6 +7,9 @@
 #include <unordered_map>
 #include <vector>
 
+#include "absl/strings/string_view.h"
+
+#include "MQMessageQueue.h"
 #include "MessageType.h"
 
 ROCKETMQ_NAMESPACE_BEGIN
@@ -18,64 +21,82 @@
 class MQMessage {
 public:
   MQMessage();
-  MQMessage(const std::string& topic, const std::string& body);
-  MQMessage(const std::string& topic, const std::string& tags, const std::string& body);
-  MQMessage(const std::string& topic, const std::string& tags, const std::string& keys, const std::string& body);
+  MQMessage(const std::string &topic, const std::string &body);
+  MQMessage(const std::string &topic, const std::string &tags,
+            const std::string &body);
+  MQMessage(const std::string &topic, const std::string &tags,
+            const std::string &keys, const std::string &body);
 
   virtual ~MQMessage();
 
-  MQMessage(const MQMessage& other);
-  MQMessage& operator=(const MQMessage& other);
+  MQMessage(const MQMessage &other);
+  MQMessage &operator=(const MQMessage &other);
 
-  const std::string& getMsgId() const;
+  const std::string &getMsgId() const;
 
-  void setProperty(const std::string& name, const std::string& value);
-  std::string getProperty(const std::string& name) const;
+  void setProperty(const std::string &name, const std::string &value);
+  std::string getProperty(const std::string &name) const;
 
-  const std::string& getTopic() const;
-  void setTopic(const std::string& topic);
-  void setTopic(const char* data, int len);
+  const std::string &getTopic() const;
+  void setTopic(const std::string &topic);
+  void setTopic(const char *data, int len);
 
   std::string getTags() const;
-  void setTags(const std::string& tags);
+  void setTags(const std::string &tags);
 
-  const std::vector<std::string>& getKeys() const;
+  const std::vector<std::string> &getKeys() const;
 
   /**
    * @brief Add a unique key for the message
-   * TODO: a message may be associated with multiple keys. setKey, actually mean attach the given key to the message.
-   * Better rename it.
+   * TODO: a message may be associated with multiple keys. setKey, actually mean
+   * attach the given key to the message. Better rename it.
    * @param key Unique key in perspective of bussiness logic.
    */
-  void setKey(const std::string& key);
-  void setKeys(const std::vector<std::string>& keys);
+  void setKey(const std::string &key);
+  void setKeys(const std::vector<std::string> &keys);
 
   int getDelayTimeLevel() const;
   void setDelayTimeLevel(int level);
 
-  const std::string& traceContext() const;
-  void traceContext(const std::string& trace_context);
+  const std::string &traceContext() const;
+  void traceContext(const std::string &trace_context);
 
   std::string getBornHost() const;
 
   std::chrono::system_clock::time_point deliveryTimestamp() const;
 
-  const std::string& getBody() const;
-  void setBody(const char* data, int len);
-  void setBody(const std::string& body);
+  const std::string &getBody() const;
+  void setBody(const char *data, int len);
+  void setBody(const std::string &body);
 
   uint32_t bodyLength() const;
 
-  const std::map<std::string, std::string>& getProperties() const;
-  void setProperties(const std::map<std::string, std::string>& properties);
+  const std::map<std::string, std::string> &getProperties() const;
+  void setProperties(const std::map<std::string, std::string> &properties);
 
   void messageType(MessageType message_type);
   MessageType messageType() const;
 
+  void bindMessageGroup(absl::string_view message_group) {
+    message_group_ = std::string(message_group.data(), message_group.length());
+  }
+
+  void bindMessageQueue(const MQMessageQueue &message_queue) {
+    message_queue_ = message_queue;
+  }
+
+  const std::string &messageGroup() const { return message_group_; }
+
+  const MQMessageQueue &messageQueue() const { return message_queue_; }
+
 protected:
-  MessageImpl* impl_;
+  MessageImpl *impl_;
 
   friend class MessageAccessor;
+
+private:
+  std::string message_group_;
+  MQMessageQueue message_queue_;
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/api/rocketmq/MQMessageQueue.h b/api/rocketmq/MQMessageQueue.h
index d7d9070..4beb347 100644
--- a/api/rocketmq/MQMessageQueue.h
+++ b/api/rocketmq/MQMessageQueue.h
@@ -13,18 +13,19 @@
   MQMessageQueue() = default;
 
   MQMessageQueue(std::string topic, std::string broker_name, int queue_id)
-      : topic_(std::move(topic)), broker_name_(std::move(broker_name)), queue_id_(queue_id) {}
+      : topic_(std::move(topic)), broker_name_(std::move(broker_name)),
+        queue_id_(queue_id) {}
 
-  MQMessageQueue(const MQMessageQueue& other) { this->operator=(other); }
+  MQMessageQueue(const MQMessageQueue &other) { this->operator=(other); }
 
-  MQMessageQueue(MQMessageQueue&& other) noexcept {
+  MQMessageQueue(MQMessageQueue &&other) noexcept {
     topic_ = std::move(other.topic_);
     broker_name_ = std::move(other.broker_name_);
     queue_id_ = other.queue_id_;
     service_address_ = other.service_address_;
   }
 
-  MQMessageQueue& operator=(const MQMessageQueue& other) {
+  MQMessageQueue &operator=(const MQMessageQueue &other) {
     if (this == &other) {
       return *this;
     }
@@ -36,27 +37,31 @@
     return *this;
   }
 
-  const std::string& getTopic() const { return topic_; }
+  const std::string &getTopic() const { return topic_; }
 
-  void setTopic(const std::string& topic) { topic_ = topic; }
+  void setTopic(const std::string &topic) { topic_ = topic; }
 
-  const std::string& getBrokerName() const { return broker_name_; }
+  const std::string &getBrokerName() const { return broker_name_; }
 
-  void setBrokerName(const std::string& broker_name) { broker_name_ = broker_name; }
+  void setBrokerName(const std::string &broker_name) {
+    broker_name_ = broker_name;
+  }
 
   int getQueueId() const { return queue_id_; }
 
   void setQueueId(int queue_id) { queue_id_ = queue_id; }
 
-  bool operator!=(const MQMessageQueue& rhs) const {
-    return topic_ != rhs.topic_ || broker_name_ != rhs.broker_name_ || queue_id_ != rhs.queue_id_;
+  bool operator!=(const MQMessageQueue &rhs) const {
+    return topic_ != rhs.topic_ || broker_name_ != rhs.broker_name_ ||
+           queue_id_ != rhs.queue_id_;
   }
 
-  bool operator==(const MQMessageQueue& mq) const {
-    return topic_ == mq.topic_ && broker_name_ == mq.broker_name_ && queue_id_ == mq.queue_id_;
+  bool operator==(const MQMessageQueue &mq) const {
+    return topic_ == mq.topic_ && broker_name_ == mq.broker_name_ &&
+           queue_id_ == mq.queue_id_;
   }
 
-  bool operator<(const MQMessageQueue& mq) const {
+  bool operator<(const MQMessageQueue &mq) const {
     if (topic_ != mq.topic_) {
       return topic_ < mq.topic_;
     }
@@ -68,7 +73,11 @@
     return queue_id_ < mq.queue_id_;
   }
 
-  int compareTo(const MQMessageQueue& mq) const {
+  operator bool() const {
+    return !topic_.empty() && !broker_name_.empty() && queue_id_ >= 0;
+  }
+
+  int compareTo(const MQMessageQueue &mq) const {
     if (this == &mq) {
       return 0;
     }
@@ -84,21 +93,22 @@
     return 0;
   }
 
-  std::string simpleName() const { return topic_ + "_" + broker_name_ + "_" + std::to_string(queue_id_); }
+  std::string simpleName() const {
+    return topic_ + "_" + broker_name_ + "_" + std::to_string(queue_id_);
+  }
 
   std::string toString() const {
     std::stringstream ss;
-    ss << "MessageQueue [topic=" << topic_ << ", brokerName=" << broker_name_ << ", queueId=" << queue_id_ << "]";
+    ss << "MessageQueue [topic=" << topic_ << ", brokerName=" << broker_name_
+       << ", queueId=" << queue_id_ << "]";
     return ss.str();
   }
 
-  template <typename H> friend H AbslHashValue(H h, const MQMessageQueue& mq) {
+  template <typename H> friend H AbslHashValue(H h, const MQMessageQueue &mq) {
     return H::combine(std::move(h), mq.topic_, mq.broker_name_, mq.queue_id_);
   }
 
-  const std::string& serviceAddress() const {
-    return service_address_;
-  }
+  const std::string &serviceAddress() const { return service_address_; }
 
   void serviceAddress(std::string service_address) {
     service_address_ = std::move(service_address);
diff --git a/api/rocketmq/SendResult.h b/api/rocketmq/SendResult.h
index 961d229..554827c 100644
--- a/api/rocketmq/SendResult.h
+++ b/api/rocketmq/SendResult.h
@@ -82,6 +82,8 @@
     trace_context_ = std::move(trace_context);
   }
 
+  operator bool() { return !message_id_.empty(); }
+
 private:
   SendStatus send_status_{SendStatus::SEND_OK};
   std::string message_id_;
diff --git a/example/rocketmq/ExampleAsyncProducer.cpp b/example/rocketmq/ExampleAsyncProducer.cpp
index a40dd02..78e23e5 100644
--- a/example/rocketmq/ExampleAsyncProducer.cpp
+++ b/example/rocketmq/ExampleAsyncProducer.cpp
@@ -1,4 +1,5 @@
 #include "rocketmq/DefaultMQProducer.h"
+#include "rocketmq/ErrorCode.h"
 #include <algorithm>
 #include <array>
 #include <atomic>
@@ -6,6 +7,7 @@
 #include <iostream>
 #include <mutex>
 #include <random>
+#include <system_error>
 
 using namespace rocketmq;
 
@@ -41,7 +43,8 @@
   return result;
 }
 
-template <int PARTITION> class RateLimiter {
+template <int PARTITION>
+class RateLimiter {
 public:
   explicit RateLimiter(int permit) : permits_{0}, interval_(1000 / PARTITION), stopped_(false) {
     int avg = permit / PARTITION;
@@ -137,9 +140,9 @@
 public:
   SampleSendCallback(std::atomic_int& counter, std::atomic_int& error) : counter_(counter), error_(error) {}
 
-  void onSuccess(SendResult& send_result) override { counter_.fetch_add(1, std::memory_order_relaxed); }
+  void onSuccess(SendResult& send_result) noexcept override { counter_.fetch_add(1, std::memory_order_relaxed); }
 
-  void onException(const MQException& e) override { error_.fetch_add(1, std::memory_order_relaxed); }
+  void onFailure(const std::error_code& ec) noexcept override { error_.fetch_add(1, std::memory_order_relaxed); }
 
 private:
   std::atomic_int& counter_;
diff --git a/src/main/cpp/base/ErrorCategory.cpp b/src/main/cpp/base/ErrorCategory.cpp
new file mode 100644
index 0000000..9d40725
--- /dev/null
+++ b/src/main/cpp/base/ErrorCategory.cpp
@@ -0,0 +1,89 @@
+#include "rocketmq/ErrorCategory.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+std::string ErrorCategory::message(int code) const {
+  ErrorCode ec = static_cast<ErrorCode>(code);
+  switch (ec) {
+  case ErrorCode::Success:
+    return "Success";
+
+  case ErrorCode::IllegalState:
+    return "Client state illegal. Forgot to call start()?";
+
+  case ErrorCode::BadConfiguration:
+    return "Bad configuration.";
+
+  case ErrorCode::BadRequest:
+    return "Message is ill-formed. Check validity of your topic, tag, "
+           "etc";
+
+  case ErrorCode::Unauthorized:
+    return "Authentication failed. Possibly caused by invalid credentials.";
+
+  case ErrorCode::Forbidden:
+    return "Authenticated user does not have privilege to perform the "
+           "requested action";
+
+  case ErrorCode::NotFound:
+    return "Topic not found, which should be created through console or "
+           "administration API before hand.";
+
+  case ErrorCode::RequestTimeout:
+    return "Timeout when connecting, reading from or writing to brokers.";
+
+  case ErrorCode::PayloadTooLarge:
+    return "Message body is too large.";
+
+  case ErrorCode::PreconditionRequired:
+    return "State of dependent procedure is not right";
+
+  case ErrorCode::TooManyRequest:
+    return "Quota exchausted. The user has sent too many requests in a given "
+           "amount of time.";
+
+  case ErrorCode::UnavailableForLegalReasons:
+    return "A server operator has received a legal demand to deny access to "
+           "a resource or to a set of resources that "
+           "includes the requested resource.";
+
+  case ErrorCode::HeaderFieldsTooLarge:
+    return "The server is unwilling to process the request because either an "
+           "individual header field, or all the header fields collectively, "
+           "are too large";
+
+  case ErrorCode::InternalServerError:
+    return "Server side interval error";
+
+  case ErrorCode::NotImplemented:
+    return "The server either does not recognize the request method, or it "
+           "lacks the ability to fulfil the request.";
+
+  case ErrorCode::BadGateway:
+    return "The server was acting as a gateway or proxy and received an "
+           "invalid response from the upstream server.";
+
+  case ErrorCode::ServiceUnavailable:
+    return "The server cannot handle the request (because it is overloaded "
+           "or down for maintenance). Generally, this "
+           "is a temporary state.";
+
+  case ErrorCode::GatewayTimeout:
+    return "The server was acting as a gateway or proxy and did not receive "
+           "a timely response from the upstream "
+           "server.";
+
+  case ErrorCode::ProtocolVersionNotSupported:
+    return "The server does not support the protocol version used in the "
+           "request.";
+
+  case ErrorCode::InsufficientStorage:
+    return "The server is unable to store the representation needed to "
+           "complete the request.";
+
+  default:
+    return "Not-Implemented";
+  }
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/base/ErrorCode.cpp b/src/main/cpp/base/ErrorCode.cpp
new file mode 100644
index 0000000..b6e44dc
--- /dev/null
+++ b/src/main/cpp/base/ErrorCode.cpp
@@ -0,0 +1,10 @@
+#include "rocketmq/ErrorCategory.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+std::error_code make_error_code(ErrorCode code) {
+  const ErrorCategory& instance = ErrorCategory::instance();
+  return {static_cast<int>(code), instance};
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/client/ClientManagerImpl.cpp b/src/main/cpp/client/ClientManagerImpl.cpp
index a86ff5e..679ce3f 100644
--- a/src/main/cpp/client/ClientManagerImpl.cpp
+++ b/src/main/cpp/client/ClientManagerImpl.cpp
@@ -1,5 +1,14 @@
 #include "ClientManagerImpl.h"
 
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+#include "google/rpc/code.pb.h"
+
 #include "InvocationContext.h"
 #include "LogInterceptor.h"
 #include "LogInterceptorFactory.h"
@@ -16,11 +25,6 @@
 #include "grpcpp/create_channel.h"
 #include "rocketmq/ErrorCode.h"
 #include "rocketmq/MQMessageExt.h"
-#include <chrono>
-#include <google/rpc/code.pb.h>
-#include <memory>
-#include <utility>
-#include <vector>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -155,18 +159,14 @@
 void ClientManagerImpl::healthCheck(
     const std::string& target_host, const Metadata& metadata, const HealthCheckRequest& request,
     std::chrono::milliseconds timeout,
-    const std::function<void(const std::string&, const InvocationContext<HealthCheckResponse>*)>& cb) {
-  {
-    absl::MutexLock lk(&rpc_clients_mtx_);
-    if (!rpc_clients_.contains(target_host)) {
-      SPDLOG_WARN("Try to perform health check for {}, which is unknown to client manager", target_host);
-      cb(target_host, nullptr);
-      return;
-    }
-  }
-
+    const std::function<void(const std::error_code&, const InvocationContext<HealthCheckResponse>*)>& cb) {
+  std::error_code ec;
   auto client = getRpcClient(target_host);
-  assert(client);
+  if (!client) {
+    ec = ErrorCode::RequestTimeout;
+    cb(ec, nullptr);
+    return;
+  }
 
   auto invocation_context = new InvocationContext<HealthCheckResponse>();
   invocation_context->remote_address = target_host;
@@ -176,7 +176,41 @@
     invocation_context->context.AddMetadata(entry.first, entry.second);
   }
 
-  auto callback = [cb](const InvocationContext<HealthCheckResponse>* ctx) { cb(ctx->remote_address, ctx); };
+  auto callback = [cb](const InvocationContext<HealthCheckResponse>* ctx) {
+    std::error_code ec;
+    if (!ctx->status.ok()) {
+      ec = ErrorCode::RequestTimeout;
+      cb(ec, ctx);
+      return;
+    }
+
+    const auto& common = ctx->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        cb(ec, ctx);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+        ec = ErrorCode::Unauthorized;
+        cb(ec, ctx);
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+        ec = ErrorCode::Forbidden;
+        cb(ec, ctx);
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}", common.status().message());
+        ec = ErrorCode::InternalServerError;
+        cb(ec, ctx);
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplemented: please upgrade SDK to latest release");
+        ec = ErrorCode::NotImplemented;
+        cb(ec, ctx);
+      } break;
+    }
+  };
 
   invocation_context->callback = callback;
   client->asyncHealthCheck(request, invocation_context);
@@ -190,7 +224,7 @@
     return;
   }
 
-  cleanOfflineRpcClients();
+  auto&& rpc_clients_removed = cleanOfflineRpcClients();
 
   std::vector<std::shared_ptr<Client>> clients;
   {
@@ -203,13 +237,19 @@
     }
   }
 
+  if (!rpc_clients_removed.empty()) {
+    for (auto& client : clients) {
+      client->onRemoteEndpointRemoval(rpc_clients_removed);
+    }
+  }
+
   for (auto& client : clients) {
     client->healthCheck();
   }
   SPDLOG_DEBUG("Health check completed");
 }
 
-void ClientManagerImpl::cleanOfflineRpcClients() {
+std::vector<std::string> ClientManagerImpl::cleanOfflineRpcClients() {
   absl::flat_hash_set<std::string> hosts;
   {
     absl::MutexLock lk(&clients_mtx_);
@@ -222,23 +262,27 @@
     }
   }
 
+  std::vector<std::string> removed;
   {
     absl::MutexLock lk(&rpc_clients_mtx_);
     for (auto it = rpc_clients_.begin(); it != rpc_clients_.end();) {
       std::string host = it->first;
       if (it->second->needHeartbeat() && !hosts.contains(host)) {
         SPDLOG_INFO("Removed RPC client whose peer is offline. RemoteHost={}", host);
+        removed.push_back(host);
         rpc_clients_.erase(it++);
       } else {
         it++;
       }
     }
   }
+
+  return removed;
 }
 
 void ClientManagerImpl::heartbeat(const std::string& target_host, const Metadata& metadata,
                                   const HeartbeatRequest& request, std::chrono::milliseconds timeout,
-                                  const std::function<void(bool, const HeartbeatResponse&)>& cb) {
+                                  const std::function<void(const std::error_code&, const HeartbeatResponse&)>& cb) {
   auto client = getRpcClient(target_host, true);
   if (!client) {
     return;
@@ -251,22 +295,47 @@
   }
 
   auto callback = [cb](const InvocationContext<HeartbeatResponse>* invocation_context) {
-    if (invocation_context->status.ok()) {
-      if (google::rpc::Code::OK == invocation_context->response.common().status().code()) {
-        SPDLOG_DEBUG("Send heartbeat to target_host={}, gRPC status OK", invocation_context->remote_address);
-        cb(true, invocation_context->response);
-      } else {
-        SPDLOG_WARN("Server[{}] failed to process heartbeat. Reason: {}", invocation_context->remote_address,
-                    invocation_context->response.common().DebugString());
-        cb(false, invocation_context->response);
-      }
-    } else {
-      SPDLOG_WARN("Failed to send heartbeat to target_host={}. GRPC code: {}, message : {}",
+    if (!invocation_context->status.ok()) {
+      SPDLOG_WARN("Failed to send heartbeat to target_host={}. gRPC code: {}, message: {}",
                   invocation_context->remote_address, invocation_context->status.error_code(),
                   invocation_context->status.error_message());
-      cb(false, invocation_context->response);
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb(ec, invocation_context->response);
+      return;
+    }
+
+    const auto& common = invocation_context->response.common();
+    std::error_code ec;
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+        ec = ErrorCode::Unauthorized;
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+        ec = ErrorCode::Forbidden;
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::INVALID_ARGUMENT: {
+        SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+        ec = ErrorCode::BadRequest;
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}", common.status().message());
+        ec = ErrorCode::InternalServerError;
+        cb(ec, invocation_context->response);
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplemented: Please upgrade SDK to latest release");
+      } break;
     }
   };
+
   invocation_context->callback = callback;
   invocation_context->context.set_deadline(std::chrono::system_clock::now() + timeout);
   client->asyncHeartbeat(request, invocation_context);
@@ -296,7 +365,6 @@
 }
 
 void ClientManagerImpl::pollCompletionQueue() {
-
   while (State::STARTED == state_.load(std::memory_order_relaxed) ||
          State::STARTING == state_.load(std::memory_order_relaxed)) {
     bool ok = false;
@@ -328,40 +396,65 @@
   }
 
   const std::string& topic = request.message().topic().name();
-  auto completion_callback = [topic, cb, this](const InvocationContext<SendMessageResponse>* invocation_context) {
-    if (invocation_context->status.ok() &&
-        google::rpc::Code::OK == invocation_context->response.common().status().code()) {
-      SendResult send_result;
-      send_result.setSendStatus(SendStatus::SEND_OK);
-      send_result.setMsgId(invocation_context->response.message_id());
-      send_result.setTransactionId(invocation_context->response.transaction_id());
-      if (State::STARTED == state_.load(std::memory_order_relaxed)) {
-        cb->onSuccess(send_result);
-      } else {
-        SPDLOG_INFO("Client instance has stopped, state={}. Message[MessageId={}] ignored",
-                    state_.load(std::memory_order_relaxed), send_result.getMsgId());
-      }
-    } else {
-      if (!invocation_context->status.ok()) {
-        SPDLOG_WARN("Failed to send message to {} due to gRPC error. gRPC code: {}, gRPC error message: {}",
-                    invocation_context->remote_address, invocation_context->status.error_code(),
-                    invocation_context->status.error_message());
-      }
-      std::string msg;
-      msg.append("gRPC code: ")
-          .append(std::to_string(invocation_context->status.error_code()))
-          .append(", gRPC message: ")
-          .append(invocation_context->status.error_message())
-          .append(", code: ")
-          .append(std::to_string(invocation_context->response.common().status().code()))
-          .append(", remark: ")
-          .append(invocation_context->response.common().DebugString());
-      MQException e(msg, FAILED_TO_SEND_MESSAGE, __FILE__, __LINE__);
-      if (State::STARTED == state_.load(std::memory_order_relaxed)) {
-        cb->onException(e);
-      } else {
-        SPDLOG_WARN("Client instance has stopped, state={}. Ignore exception raised while sending message: {}",
-                    state_.load(std::memory_order_relaxed), e.what());
+  std::weak_ptr<ClientManager> client_manager(shared_from_this());
+  auto completion_callback = [topic, cb,
+                              client_manager](const InvocationContext<SendMessageResponse>* invocation_context) {
+    ClientManagerPtr client_manager_ptr = client_manager.lock();
+    if (!client_manager_ptr) {
+      return;
+    }
+
+    if (State::STARTED != client_manager_ptr->state()) {
+      // TODO: Would this leak some memroy?
+      return;
+    }
+
+    const auto& common = invocation_context->response.common();
+
+    if (!invocation_context->status.ok()) {
+      SPDLOG_WARN("Failed to send message to {} due to gRPC error. gRPC code: {}, gRPC error message: {}",
+                  invocation_context->remote_address, invocation_context->status.error_code(),
+                  invocation_context->status.error_message());
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb->onFailure(ec);
+      return;
+    }
+
+    if (invocation_context->status.ok()) {
+      switch (invocation_context->response.common().status().code()) {
+        case google::rpc::Code::OK: {
+          SendResult send_result;
+          send_result.setSendStatus(SendStatus::SEND_OK);
+          send_result.setMsgId(invocation_context->response.message_id());
+          send_result.setTransactionId(invocation_context->response.transaction_id());
+          cb->onSuccess(send_result);
+        } break;
+
+        case google::rpc::Code::INVALID_ARGUMENT: {
+          SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+          std::error_code ec = ErrorCode::BadRequest;
+          cb->onFailure(ec);
+        } break;
+        case google::rpc::Code::UNAUTHENTICATED: {
+          SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+          std::error_code ec = ErrorCode::Unauthorized;
+          cb->onFailure(ec);
+        } break;
+        case google::rpc::Code::PERMISSION_DENIED: {
+          SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+          std::error_code ec = ErrorCode::Forbidden;
+          cb->onFailure(ec);
+        } break;
+        case google::rpc::Code::INTERNAL: {
+          SPDLOG_WARN("InternalServerError: {}", common.status().message());
+          std::error_code ec = ErrorCode::InternalServerError;
+          cb->onFailure(ec);
+        } break;
+        default: {
+          SPDLOG_WARN("Unsupported status code. Check and upgrade SDK to the latest");
+          std::error_code ec = ErrorCode::NotImplemented;
+          cb->onFailure(ec);
+        } break;
       }
     }
   };
@@ -447,12 +540,13 @@
 
 void ClientManagerImpl::resolveRoute(const std::string& target_host, const Metadata& metadata,
                                      const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                     const std::function<void(bool, const TopicRouteDataPtr&)>& cb) {
+                                     const std::function<void(const std::error_code&, const TopicRouteDataPtr&)>& cb) {
 
   RpcClientSharedPtr client = getRpcClient(target_host, false);
   if (!client) {
     SPDLOG_WARN("Failed to create RPC client for name server[host={}]", target_host);
-    cb(false, nullptr);
+    std::error_code ec = ErrorCode::RequestTimeout;
+    cb(ec, nullptr);
     return;
   }
 
@@ -467,94 +561,144 @@
     if (!invocation_context->status.ok()) {
       SPDLOG_WARN("Failed to send query route request to server[host={}]. Reason: {}",
                   invocation_context->remote_address, invocation_context->status.error_message());
-      cb(false, nullptr);
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb(ec, nullptr);
       return;
     }
 
-    if (google::rpc::Code::OK != invocation_context->response.common().status().code()) {
-      SPDLOG_WARN("Server[host={}] failed to process query route request. Reason: {}",
-                  invocation_context->remote_address, invocation_context->response.common().DebugString());
-      cb(false, nullptr);
-      return;
+    std::error_code ec;
+    const auto& common = invocation_context->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        auto& partitions = invocation_context->response.partitions();
+        std::vector<Partition> topic_partitions;
+        for (const auto& partition : partitions) {
+          Topic t(partition.topic().resource_namespace(), partition.topic().name());
+
+          auto& broker = partition.broker();
+          AddressScheme scheme = AddressScheme::IPv4;
+          switch (broker.endpoints().scheme()) {
+            case rmq::AddressScheme::IPv4:
+              scheme = AddressScheme::IPv4;
+              break;
+            case rmq::AddressScheme::IPv6:
+              scheme = AddressScheme::IPv6;
+              break;
+            case rmq::AddressScheme::DOMAIN_NAME:
+              scheme = AddressScheme::DOMAIN_NAME;
+              break;
+            default:
+              break;
+          }
+
+          std::vector<Address> addresses;
+          for (const auto& address : broker.endpoints().addresses()) {
+            addresses.emplace_back(Address{address.host(), address.port()});
+          }
+          ServiceAddress service_address(scheme, addresses);
+          Broker b(partition.broker().name(), partition.broker().id(), service_address);
+
+          Permission permission = Permission::READ_WRITE;
+          switch (partition.permission()) {
+            case rmq::Permission::READ:
+              permission = Permission::READ;
+              break;
+
+            case rmq::Permission::WRITE:
+              permission = Permission::WRITE;
+              break;
+            case rmq::Permission::READ_WRITE:
+              permission = Permission::READ_WRITE;
+              break;
+            default:
+              break;
+          }
+          Partition topic_partition(t, partition.id(), permission, std::move(b));
+          topic_partitions.emplace_back(std::move(topic_partition));
+        }
+        auto ptr =
+            std::make_shared<TopicRouteData>(std::move(topic_partitions), invocation_context->response.DebugString());
+        cb(ec, ptr);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+        ec = ErrorCode::Unauthorized;
+        cb(ec, nullptr);
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+        ec = ErrorCode::Forbidden;
+        cb(ec, nullptr);
+      } break;
+      case google::rpc::Code::INVALID_ARGUMENT: {
+        SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+        ec = ErrorCode::BadRequest;
+        cb(ec, nullptr);
+      } break;
+      case google::rpc::Code::NOT_FOUND: {
+        SPDLOG_WARN("NotFound: {}", common.status().message());
+        ec = ErrorCode::NotFound;
+        cb(ec, nullptr);
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}", common.status().message());
+        ec = ErrorCode::InternalServerError;
+        cb(ec, nullptr);
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplement: Please upgrade to latest SDK release");
+        ec = ErrorCode::NotImplemented;
+        cb(ec, nullptr);
+      } break;
     }
-
-    auto& partitions = invocation_context->response.partitions();
-
-    std::vector<Partition> topic_partitions;
-    for (const auto& partition : partitions) {
-      Topic t(partition.topic().resource_namespace(), partition.topic().name());
-
-      auto& broker = partition.broker();
-      AddressScheme scheme = AddressScheme::IPv4;
-      switch (broker.endpoints().scheme()) {
-      case rmq::AddressScheme::IPv4:
-        scheme = AddressScheme::IPv4;
-        break;
-      case rmq::AddressScheme::IPv6:
-        scheme = AddressScheme::IPv6;
-        break;
-      case rmq::AddressScheme::DOMAIN_NAME:
-        scheme = AddressScheme::DOMAIN_NAME;
-        break;
-      default:
-        break;
-      }
-
-      std::vector<Address> addresses;
-      for (const auto& address : broker.endpoints().addresses()) {
-        addresses.emplace_back(Address{address.host(), address.port()});
-      }
-      ServiceAddress service_address(scheme, addresses);
-      Broker b(partition.broker().name(), partition.broker().id(), service_address);
-
-      Permission permission = Permission::READ_WRITE;
-      switch (partition.permission()) {
-      case rmq::Permission::READ:
-        permission = Permission::READ;
-        break;
-
-      case rmq::Permission::WRITE:
-        permission = Permission::WRITE;
-        break;
-      case rmq::Permission::READ_WRITE:
-        permission = Permission::READ_WRITE;
-        break;
-      default:
-        break;
-      }
-      Partition topic_partition(t, partition.id(), permission, std::move(b));
-      topic_partitions.emplace_back(std::move(topic_partition));
-    }
-
-    auto ptr =
-        std::make_shared<TopicRouteData>(std::move(topic_partitions), invocation_context->response.DebugString());
-    cb(true, ptr);
   };
   invocation_context->callback = callback;
   client->asyncQueryRoute(request, invocation_context);
 }
 
-void ClientManagerImpl::queryAssignment(const std::string& target, const Metadata& metadata,
-                                        const QueryAssignmentRequest& request, std::chrono::milliseconds timeout,
-                                        const std::function<void(bool, const QueryAssignmentResponse&)>& cb) {
+void ClientManagerImpl::queryAssignment(
+    const std::string& target, const Metadata& metadata, const QueryAssignmentRequest& request,
+    std::chrono::milliseconds timeout,
+    const std::function<void(const std::error_code&, const QueryAssignmentResponse&)>& cb) {
   SPDLOG_DEBUG("Prepare to send query assignment request to broker[address={}]", target);
   std::shared_ptr<RpcClient> client = getRpcClient(target);
 
   auto callback = [&, cb](const InvocationContext<QueryAssignmentResponse>* invocation_context) {
     if (!invocation_context->status.ok()) {
       SPDLOG_WARN("Failed to query assignment. Reason: {}", invocation_context->status.error_message());
-      cb(false, invocation_context->response);
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb(ec, invocation_context->response);
       return;
     }
 
-    if (google::rpc::Code::OK != invocation_context->response.common().status().code()) {
-      SPDLOG_WARN("Server[host={}] failed to process query assignment request. Reason: {}",
-                  invocation_context->remote_address, invocation_context->response.common().DebugString());
-      cb(false, invocation_context->response);
-      return;
+    const auto& common = invocation_context->response.common();
+    std::error_code ec;
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        SPDLOG_DEBUG("Query assignment OK");
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+        ec = ErrorCode::Unauthorized;
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+        ec = ErrorCode::Forbidden;
+      } break;
+      case google::rpc::Code::INVALID_ARGUMENT: {
+        SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+        ec = ErrorCode::BadRequest;
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}", common.status().message());
+        ec = ErrorCode::InternalServerError;
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplemented: please upgrade SDK to latest release");
+        ec = ErrorCode::NotImplemented;
+      } break;
     }
-
-    cb(true, invocation_context->response);
+    cb(ec, invocation_context->response);
   };
 
   auto invocation_context = new InvocationContext<QueryAssignmentResponse>();
@@ -585,17 +729,57 @@
   auto callback = [this, cb](const InvocationContext<ReceiveMessageResponse>* invocation_context) {
     if (invocation_context->status.ok()) {
       SPDLOG_DEBUG("Received pop response through gRPC from brokerAddress={}", invocation_context->remote_address);
-      ReceiveMessageResult receive_result;
-      this->processPopResult(invocation_context->context, invocation_context->response, receive_result,
-                             invocation_context->remote_address);
-      cb->onSuccess(receive_result);
+      const auto& common = invocation_context->response.common();
+      switch (common.status().code()) {
+        case google::rpc::Code::OK: {
+          ReceiveMessageResult receive_result;
+          this->processPopResult(invocation_context->context, invocation_context->response, receive_result,
+                                 invocation_context->remote_address);
+          cb->onSuccess(receive_result);
+        } break;
+
+        case google::rpc::Code::UNAUTHENTICATED: {
+          SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+          std::error_code ec = ErrorCode::Unauthorized;
+          cb->onFailure(ec);
+        } break;
+
+        case google::rpc::Code::PERMISSION_DENIED: {
+          SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+          std::error_code ec = ErrorCode::Forbidden;
+          cb->onFailure(ec);
+        } break;
+
+        case google::rpc::Code::INVALID_ARGUMENT: {
+          SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+          std::error_code ec = ErrorCode::BadRequest;
+          cb->onFailure(ec);
+        } break;
+
+        case google::rpc::Code::DEADLINE_EXCEEDED: {
+          SPDLOG_WARN("DeadlineExceeded: {}", common.status().message());
+          std::error_code ec = ErrorCode::GatewayTimeout;
+          cb->onFailure(ec);
+        } break;
+
+        case google::rpc::Code::INTERNAL: {
+          SPDLOG_WARN("IntervalServerError: {}", common.status().message());
+          std::error_code ec = ErrorCode::InternalServerError;
+          cb->onFailure(ec);
+        } break;
+        default: {
+          SPDLOG_WARN("Unsupported code. Please upgrade to use the latest release");
+          std::error_code ec = ErrorCode::NotImplemented;
+          cb->onFailure(ec);
+        } break;
+      }
+
     } else {
       SPDLOG_WARN("Failed to pop messages through GRPC from {}, gRPC code: {}, gRPC error message: {}",
                   invocation_context->remote_address, invocation_context->status.error_code(),
                   invocation_context->status.error_message());
-      MQException e(invocation_context->status.error_message(), FAILED_TO_POP_MESSAGE_ASYNCHRONOUSLY, __FILE__,
-                    __LINE__);
-      cb->onException(e);
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb->onFailure(ec);
     }
   };
   invocation_context->callback = callback;
@@ -608,25 +792,31 @@
   // process response to result
   ReceiveMessageStatus status;
   switch (response.common().status().code()) {
-  case google::rpc::Code::OK:
-    status = ReceiveMessageStatus::OK;
-    break;
-  case google::rpc::Code::RESOURCE_EXHAUSTED:
-    status = ReceiveMessageStatus::RESOURCE_EXHAUSTED;
-    SPDLOG_WARN("Too many pop requests in broker. Long polling is full in the broker side. BrokerAddress={}",
-                target_host);
-    break;
-  case google::rpc::Code::DEADLINE_EXCEEDED:
-    status = ReceiveMessageStatus::DEADLINE_EXCEEDED;
-    break;
-  case google::rpc::Code::NOT_FOUND:
-    status = ReceiveMessageStatus::NOT_FOUND;
-    break;
-  default:
-    SPDLOG_WARN("Pop response indicates server-side error. BrokerAddress={}, Reason={}", target_host,
-                response.common().DebugString());
-    status = ReceiveMessageStatus::INTERNAL;
-    break;
+    case google::rpc::Code::OK: {
+      status = ReceiveMessageStatus::OK;
+      break;
+    }
+    case google::rpc::Code::RESOURCE_EXHAUSTED: {
+      status = ReceiveMessageStatus::RESOURCE_EXHAUSTED;
+      SPDLOG_WARN("Too many pop requests in broker. Long polling is full in the broker side. BrokerAddress={}",
+                  target_host);
+      break;
+    }
+
+    case google::rpc::Code::DEADLINE_EXCEEDED: {
+      status = ReceiveMessageStatus::DEADLINE_EXCEEDED;
+      break;
+    }
+    case google::rpc::Code::NOT_FOUND: {
+      status = ReceiveMessageStatus::NOT_FOUND;
+      break;
+    }
+    default: {
+      SPDLOG_WARN("Pop response indicates server-side error. BrokerAddress={}, Reason={}", target_host,
+                  response.common().DebugString());
+      status = ReceiveMessageStatus::INTERNAL;
+      break;
+    }
   }
 
   result.sourceHost(target_host);
@@ -667,34 +857,42 @@
   }
   result.sourceHost(target_host);
   switch (response.common().status().code()) {
-  case google::rpc::Code::OK: {
-    assert(!response.messages().empty());
-    result.status_ = ReceiveMessageStatus::OK;
-    for (const auto& item : response.messages()) {
-      MQMessageExt message;
-      if (!wrapMessage(item, message)) {
-        result.status_ = ReceiveMessageStatus::DATA_CORRUPTED;
-        return;
+    case google::rpc::Code::OK: {
+      assert(!response.messages().empty());
+      result.status_ = ReceiveMessageStatus::OK;
+      for (const auto& item : response.messages()) {
+        MQMessageExt message;
+        if (!wrapMessage(item, message)) {
+          result.status_ = ReceiveMessageStatus::DATA_CORRUPTED;
+          return;
+        }
+        result.messages_.emplace_back(message);
       }
-      result.messages_.emplace_back(message);
+      break;
     }
-  } break;
 
-  case google::rpc::Code::DEADLINE_EXCEEDED:
-    result.status_ = ReceiveMessageStatus::DEADLINE_EXCEEDED;
-    break;
+    case google::rpc::Code::DEADLINE_EXCEEDED: {
+      result.status_ = ReceiveMessageStatus::DEADLINE_EXCEEDED;
+      break;
+    }
 
-  case google::rpc::Code::RESOURCE_EXHAUSTED:
-    result.status_ = ReceiveMessageStatus::RESOURCE_EXHAUSTED;
-    break;
+    case google::rpc::Code::RESOURCE_EXHAUSTED: {
+      result.status_ = ReceiveMessageStatus::RESOURCE_EXHAUSTED;
+      break;
+    }
 
-  case google::rpc::Code::OUT_OF_RANGE:
-    result.status_ = ReceiveMessageStatus::OUT_OF_RANGE;
-    result.next_offset_ = response.next_offset();
-    break;
+    case google::rpc::Code::OUT_OF_RANGE: {
+      result.status_ = ReceiveMessageStatus::OUT_OF_RANGE;
+      result.next_offset_ = response.next_offset();
+      break;
+    }
   }
 }
 
+State ClientManagerImpl::state() const {
+  return state_.load(std::memory_order_relaxed);
+}
+
 bool ClientManagerImpl::wrapMessage(const rmq::Message& item, MQMessageExt& message_ext) {
   assert(item.topic().resource_namespace() == resource_namespace_);
 
@@ -728,57 +926,57 @@
     body_digest_match = true;
   } else {
     switch (digest.type()) {
-    case rmq::DigestType::CRC32: {
-      std::string checksum;
-      bool success = MixAll::crc32(item.body(), checksum);
-      if (success) {
-        body_digest_match = (digest.checksum() == checksum);
-        if (body_digest_match) {
-          SPDLOG_DEBUG("Message body CRC32 checksum validation passed.");
+      case rmq::DigestType::CRC32: {
+        std::string checksum;
+        bool success = MixAll::crc32(item.body(), checksum);
+        if (success) {
+          body_digest_match = (digest.checksum() == checksum);
+          if (body_digest_match) {
+            SPDLOG_DEBUG("Message body CRC32 checksum validation passed.");
+          } else {
+            SPDLOG_WARN("Body CRC32 checksum validation failed. Actual: {}, expect: {}", checksum, digest.checksum());
+          }
         } else {
-          SPDLOG_WARN("Body CRC32 checksum validation failed. Actual: {}, expect: {}", checksum, digest.checksum());
+          SPDLOG_WARN("Failed to calculate CRC32 checksum. Skip.");
         }
-      } else {
-        SPDLOG_WARN("Failed to calculate CRC32 checksum. Skip.");
+        break;
       }
-      break;
-    }
-    case rmq::DigestType::MD5: {
-      std::string checksum;
-      bool success = MixAll::md5(item.body(), checksum);
-      if (success) {
-        body_digest_match = (digest.checksum() == checksum);
-        if (body_digest_match) {
-          SPDLOG_DEBUG("MD5 checksum validation passed.");
+      case rmq::DigestType::MD5: {
+        std::string checksum;
+        bool success = MixAll::md5(item.body(), checksum);
+        if (success) {
+          body_digest_match = (digest.checksum() == checksum);
+          if (body_digest_match) {
+            SPDLOG_DEBUG("MD5 checksum validation passed.");
+          } else {
+            SPDLOG_WARN("Body MD5 checksum validation failed. Expect: {}, Actual: {}", digest.checksum(), checksum);
+          }
         } else {
-          SPDLOG_WARN("Body MD5 checksum validation failed. Expect: {}, Actual: {}", digest.checksum(), checksum);
+          SPDLOG_WARN("Failed to calculate MD5 digest. Skip.");
+          body_digest_match = true;
         }
-      } else {
-        SPDLOG_WARN("Failed to calculate MD5 digest. Skip.");
+        break;
+      }
+      case rmq::DigestType::SHA1: {
+        std::string checksum;
+        bool success = MixAll::sha1(item.body(), checksum);
+        if (success) {
+          body_digest_match = (checksum == digest.checksum());
+          if (body_digest_match) {
+            SPDLOG_DEBUG("SHA1 checksum validation passed");
+          } else {
+            SPDLOG_WARN("Body SHA1 checksum validation failed. Expect: {}, Actual: {}", digest.checksum(), checksum);
+          }
+        } else {
+          SPDLOG_WARN("Failed to calculate SHA1 digest. Skip.");
+        }
+        break;
+      }
+      default: {
+        SPDLOG_WARN("Unsupported message body digest algorithm");
         body_digest_match = true;
+        break;
       }
-      break;
-    }
-    case rmq::DigestType::SHA1: {
-      std::string checksum;
-      bool success = MixAll::sha1(item.body(), checksum);
-      if (success) {
-        body_digest_match = (checksum == digest.checksum());
-        if (body_digest_match) {
-          SPDLOG_DEBUG("SHA1 checksum validation passed");
-        } else {
-          SPDLOG_WARN("Body SHA1 checksum validation failed. Expect: {}, Actual: {}", digest.checksum(), checksum);
-        }
-      } else {
-        SPDLOG_WARN("Failed to calculate SHA1 digest. Skip.");
-      }
-      break;
-    }
-    default: {
-      SPDLOG_WARN("Unsupported message body digest algorithm");
-      body_digest_match = true;
-      break;
-    }
     }
   }
 
@@ -790,20 +988,20 @@
 
   // Body encoding
   switch (system_attributes.body_encoding()) {
-  case rmq::Encoding::GZIP: {
-    std::string uncompressed;
-    UtilAll::uncompress(item.body(), uncompressed);
-    message_ext.setBody(uncompressed);
-    break;
-  }
-  case rmq::Encoding::IDENTITY: {
-    message_ext.setBody(item.body());
-    break;
-  }
-  default: {
-    SPDLOG_WARN("Unsupported encoding algorithm");
-    break;
-  }
+    case rmq::Encoding::GZIP: {
+      std::string uncompressed;
+      UtilAll::uncompress(item.body(), uncompressed);
+      message_ext.setBody(uncompressed);
+      break;
+    }
+    case rmq::Encoding::IDENTITY: {
+      message_ext.setBody(item.body());
+      break;
+    }
+    default: {
+      SPDLOG_WARN("Unsupported encoding algorithm");
+      break;
+    }
   }
 
   timeval tv{};
@@ -811,22 +1009,22 @@
   // Message-type
   MessageType message_type;
   switch (system_attributes.message_type()) {
-  case rmq::MessageType::NORMAL:
-    message_type = MessageType::NORMAL;
-    break;
-  case rmq::MessageType::FIFO:
-    message_type = MessageType::FIFO;
-    break;
-  case rmq::MessageType::DELAY:
-    message_type = MessageType::DELAY;
-    break;
-  case rmq::MessageType::TRANSACTION:
-    message_type = MessageType::TRANSACTION;
-    break;
-  default:
-    SPDLOG_WARN("Unknown message type. Treat it as normal message");
-    message_type = MessageType::NORMAL;
-    break;
+    case rmq::MessageType::NORMAL:
+      message_type = MessageType::NORMAL;
+      break;
+    case rmq::MessageType::FIFO:
+      message_type = MessageType::FIFO;
+      break;
+    case rmq::MessageType::DELAY:
+      message_type = MessageType::DELAY;
+      break;
+    case rmq::MessageType::TRANSACTION:
+      message_type = MessageType::TRANSACTION;
+      break;
+    default:
+      SPDLOG_WARN("Unknown message type. Treat it as normal message");
+      message_type = MessageType::NORMAL;
+      break;
   }
   MessageAccessor::setMessageType(message_ext, message_type);
 
@@ -853,20 +1051,20 @@
 
   // Process one-of: delivery-timestamp and delay-level.
   switch (system_attributes.timed_delivery_case()) {
-  case rmq::SystemAttribute::TimedDeliveryCase::kDelayLevel: {
-    message_ext.setDelayTimeLevel(system_attributes.delay_level());
-    break;
-  }
+    case rmq::SystemAttribute::TimedDeliveryCase::kDelayLevel: {
+      message_ext.setDelayTimeLevel(system_attributes.delay_level());
+      break;
+    }
 
-  case rmq::SystemAttribute::TimedDeliveryCase::kDeliveryTimestamp: {
-    tv.tv_sec = system_attributes.delivery_timestamp().seconds();
-    tv.tv_usec = system_attributes.delivery_timestamp().nanos();
-    MessageAccessor::setDeliveryTimestamp(message_ext, absl::TimeFromTimeval(tv));
-    break;
-  }
+    case rmq::SystemAttribute::TimedDeliveryCase::kDeliveryTimestamp: {
+      tv.tv_sec = system_attributes.delivery_timestamp().seconds();
+      tv.tv_usec = system_attributes.delivery_timestamp().nanos();
+      MessageAccessor::setDeliveryTimestamp(message_ext, absl::TimeFromTimeval(tv));
+      break;
+    }
 
-  default:
-    break;
+    default:
+      break;
   }
 
   // Partition-id
@@ -908,10 +1106,12 @@
   return true;
 }
 
-Scheduler& ClientManagerImpl::getScheduler() { return scheduler_; }
+Scheduler& ClientManagerImpl::getScheduler() {
+  return scheduler_;
+}
 
 void ClientManagerImpl::ack(const std::string& target, const Metadata& metadata, const AckMessageRequest& request,
-                            std::chrono::milliseconds timeout, const std::function<void(bool)>& cb) {
+                            std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& cb) {
   std::string target_host(target.data(), target.length());
   SPDLOG_DEBUG("Prepare to ack message against {} asynchronously. AckMessageRequest: {}", target_host,
                request.DebugString());
@@ -927,12 +1127,40 @@
 
   // TODO: Use capture by move and pass-by-value paradigm when C++ 14 is available.
   auto callback = [request, cb](const InvocationContext<AckMessageResponse>* invocation_context) {
-    if (invocation_context->status.ok() &&
-        google::rpc::Code::OK == invocation_context->response.common().status().code()) {
-      cb(true);
-    } else {
-      cb(false);
+    std::error_code ec;
+    if (!invocation_context->status.ok()) {
+      ec = ErrorCode::RequestTimeout;
+      cb(ec);
+      return;
     }
+
+    const auto& common = invocation_context->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        SPDLOG_DEBUG("Ack OK. host={}", invocation_context->remote_address);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Unauthorized;
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Forbidden;
+      } break;
+      case google::rpc::Code::INVALID_ARGUMENT: {
+        SPDLOG_WARN("InvalidArgument: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::BadRequest;
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::InternalServerError;
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplement: please upgrade SDK to latest release. host={}", invocation_context->remote_address);
+        ec = ErrorCode::NotImplemented;
+      } break;
+    }
+    cb(ec);
   };
   invocation_context->callback = callback;
   client->asyncAck(request, invocation_context);
@@ -940,7 +1168,7 @@
 
 void ClientManagerImpl::nack(const std::string& target_host, const Metadata& metadata,
                              const NackMessageRequest& request, std::chrono::milliseconds timeout,
-                             const std::function<void(bool)>& completion_callback) {
+                             const std::function<void(const std::error_code&)>& completion_callback) {
   RpcClientSharedPtr client = getRpcClient(target_host);
   assert(client);
   auto invocation_context = new InvocationContext<NackMessageResponse>();
@@ -952,25 +1180,58 @@
   }
 
   auto callback = [completion_callback](const InvocationContext<NackMessageResponse>* invocation_context) {
-    if (invocation_context->status.ok() &&
-        google::rpc::Code::OK == invocation_context->response.common().status().code()) {
-      completion_callback(true);
-    } else {
-      completion_callback(false);
+    if (!invocation_context->status.ok()) {
+      SPDLOG_WARN("Failed to write Nack request to wire. gRPC-code: {}, gRPC-message: {}",
+                  invocation_context->status.error_code(), invocation_context->status.error_message());
+      std::error_code ec = ErrorCode::RequestTimeout;
+      completion_callback(ec);
+      return;
     }
+
+    std::error_code ec;
+    const auto& common = invocation_context->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        SPDLOG_DEBUG("Nack to {} OK", invocation_context->remote_address);
+        break;
+      };
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Unauthorized;
+        break;
+      }
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Forbidden;
+        break;
+      }
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::InternalServerError;
+        break;
+      }
+      default: {
+        SPDLOG_WARN("NotImplemented: Please upgrade to latest SDK, host={}", invocation_context->remote_address);
+        ec = ErrorCode::NotImplemented;
+        break;
+      }
+    }
+    completion_callback(ec);
   };
   invocation_context->callback = callback;
   client->asyncNack(request, invocation_context);
 }
 
-void ClientManagerImpl::endTransaction(const std::string& target_host, const Metadata& metadata,
-                                       const EndTransactionRequest& request, std::chrono::milliseconds timeout,
-                                       const std::function<void(bool, const EndTransactionResponse&)>& cb) {
+void ClientManagerImpl::endTransaction(
+    const std::string& target_host, const Metadata& metadata, const EndTransactionRequest& request,
+    std::chrono::milliseconds timeout,
+    const std::function<void(const std::error_code&, const EndTransactionResponse&)>& cb) {
   RpcClientSharedPtr client = getRpcClient(target_host);
   if (!client) {
     SPDLOG_WARN("No RPC client for {}", target_host);
     EndTransactionResponse response;
-    cb(false, response);
+    std::error_code ec = ErrorCode::BadRequest;
+    cb(ec, response);
     return;
   }
 
@@ -987,17 +1248,43 @@
   invocation_context->context.set_deadline(deadline);
 
   auto callback = [target_host, cb](const InvocationContext<EndTransactionResponse>* invocation_context) {
-    if (!invocation_context->status.ok() ||
-        google::rpc::Code::OK != invocation_context->response.common().status().code()) {
-      SPDLOG_WARN("Failed to endTransaction. TargetHost={}, gRPC statusCode={}, errorMessage={}", target_host.data(),
-                  invocation_context->status.error_message(), invocation_context->status.error_message());
-      cb(false, invocation_context->response);
+    std::error_code ec;
+    if (!invocation_context->status.ok()) {
+      SPDLOG_WARN("Failed to write EndTransaction to wire. gRPC-code: {}, gRPC-message: {}, host={}",
+                  invocation_context->status.error_code(), invocation_context->status.error_message(),
+                  invocation_context->remote_address);
+      ec = ErrorCode::BadRequest;
+      cb(ec, invocation_context->response);
       return;
     }
 
-    SPDLOG_DEBUG("endTransaction completed OK. Response: {}", invocation_context->response.DebugString());
-    cb(true, invocation_context->response);
+    const auto& common = invocation_context->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        SPDLOG_DEBUG("endTransaction completed OK. Response: {}, host={}", invocation_context->response.DebugString(),
+                     invocation_context->remote_address);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Unauthorized;
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Forbidden;
+      } break;
+      case google::rpc::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::InternalServerError;
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplemented: please upgrade SDK to latest release. {}, host={}", common.status().message(),
+                    invocation_context->remote_address);
+        ec = ErrorCode::NotImplemented;
+      }
+    }
+    cb(ec, invocation_context->response);
   };
+
   invocation_context->callback = callback;
   client->asyncEndTransaction(request, invocation_context);
 }
@@ -1042,10 +1329,14 @@
 
 void ClientManagerImpl::queryOffset(const std::string& target_host, const Metadata& metadata,
                                     const QueryOffsetRequest& request, std::chrono::milliseconds timeout,
-                                    const std::function<void(bool, const QueryOffsetResponse&)>& cb) {
+                                    const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) {
   auto client = getRpcClient(target_host);
+  std::error_code ec;
   if (!client) {
     SPDLOG_WARN("Failed to get/create RPC client for {}", target_host);
+    ec = ErrorCode::RequestTimeout;
+    QueryOffsetResponse response;
+    cb(ec, response);
     return;
   }
 
@@ -1053,21 +1344,45 @@
   invocation_context->remote_address = target_host;
   invocation_context->context.set_deadline(std::chrono::system_clock::now() + timeout);
   auto callback = [cb](const InvocationContext<QueryOffsetResponse>* invocation_context) {
+    std::error_code ec;
+
     if (!invocation_context->status.ok()) {
-      SPDLOG_WARN("Failed to send query offset request to {}. Reason: {}", invocation_context->remote_address,
-                  invocation_context->status.error_message());
-      cb(false, invocation_context->response);
+      SPDLOG_WARN("Failed to write QueryOffset request to wire. gRPC-code: {}, gRPC-message: {}, host={}",
+                  invocation_context->status.error_code(), invocation_context->status.error_message(),
+                  invocation_context->remote_address);
+      ec = ErrorCode::RequestTimeout;
+      cb(ec, invocation_context->response);
       return;
     }
 
-    if (google::rpc::Code::OK != invocation_context->response.common().status().code()) {
-      SPDLOG_WARN("Server[host={}] failed to process query offset request. Reason: {}",
-                  invocation_context->remote_address, invocation_context->response.common().DebugString());
-      cb(false, invocation_context->response);
+    const auto& common = invocation_context->response.common();
+    switch (common.status().code()) {
+      case google::rpc::Code::OK: {
+        SPDLOG_DEBUG("Query offset from server[host={}] OK", invocation_context->remote_address);
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::UNAUTHENTICATED: {
+        SPDLOG_WARN("Unauthenticated: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Unauthorized;
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::PERMISSION_DENIED: {
+        SPDLOG_WARN("PermissionDenied: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::Forbidden;
+        cb(ec, invocation_context->response);
+      } break;
+      case google::rpc::Code::INTERNAL: {
+        SPDLOG_WARN("InternalServerError: {}, host={}", common.status().message(), invocation_context->remote_address);
+        ec = ErrorCode::InternalServerError;
+        cb(ec, invocation_context->response);
+      } break;
+      default: {
+        SPDLOG_WARN("NotImplemented: please upgrade SDK to the latest release. host={}",
+                    invocation_context->remote_address);
+        ec = ErrorCode::NotImplemented;
+        cb(ec, invocation_context->response);
+      }
     }
-
-    SPDLOG_DEBUG("Query offset from server[host={}] OK", invocation_context->remote_address);
-    cb(true, invocation_context->response);
   };
   invocation_context->callback = callback;
   client->asyncQueryOffset(request, invocation_context);
@@ -1141,6 +1456,8 @@
   NotifyClientTerminationResponse response;
   grpc::Status status = client->notifyClientTermination(&context, request, &response);
   if (!status.ok()) {
+    SPDLOG_WARN("Failed to write NotifyClientTermination request to wire. gRPC-code: {}, gRPC-message: {}, host={}",
+                status.error_code(), status.error_message(), target_host);
     return false;
   }
 
diff --git a/src/main/cpp/client/LogInterceptor.cpp b/src/main/cpp/client/LogInterceptor.cpp
index 3497ddd..869fc95 100644
--- a/src/main/cpp/client/LogInterceptor.cpp
+++ b/src/main/cpp/client/LogInterceptor.cpp
@@ -34,8 +34,12 @@
         response_headers.insert({absl::string_view(it.first.data(), it.first.length()),
                                  absl::string_view(it.second.data(), it.second.length())});
       }
-      SPDLOG_DEBUG("[Inbound]Response Headers of {}:\n{}", client_rpc_info_->method(),
-                   absl::StrJoin(response_headers, "\n", absl::PairFormatter(" --> ")));
+      if (!response_headers.empty()) {
+        SPDLOG_DEBUG("[Inbound]Response Headers of {}:\n{}", client_rpc_info_->method(),
+                     absl::StrJoin(response_headers, "\n", absl::PairFormatter(" --> ")));
+      } else {
+        SPDLOG_DEBUG("[Inbound]Response metadata of {} is empty", client_rpc_info_->method());
+      }
     }
   }
 
diff --git a/src/main/cpp/client/include/Client.h b/src/main/cpp/client/include/Client.h
index d029598..9a14e8c 100644
--- a/src/main/cpp/client/include/Client.h
+++ b/src/main/cpp/client/include/Client.h
@@ -18,6 +18,8 @@
 
   virtual bool active() = 0;
 
+  virtual void onRemoteEndpointRemoval(const std::vector<std::string>&) = 0;
+
   /**
    * For endpoints that are marked as inactive due to one or multiple business operation failure, this function is to
    * initiate health-check RPCs; Once the health-check passes, they are conceptually add back to serve further business
diff --git a/src/main/cpp/client/include/ClientManager.h b/src/main/cpp/client/include/ClientManager.h
index b98a6d3..28d0ef1 100644
--- a/src/main/cpp/client/include/ClientManager.h
+++ b/src/main/cpp/client/include/ClientManager.h
@@ -2,6 +2,7 @@
 
 #include <chrono>
 #include <memory>
+#include <system_error>
 
 #include "Client.h"
 #include "ReceiveMessageCallback.h"
@@ -10,6 +11,7 @@
 #include "TopAddressing.h"
 #include "TopicRouteData.h"
 #include "rocketmq/MQMessageExt.h"
+#include "rocketmq/State.h"
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -29,11 +31,11 @@
 
   virtual void resolveRoute(const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
                             std::chrono::milliseconds timeout,
-                            const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) = 0;
+                            const std::function<void(const std::error_code&, const TopicRouteDataPtr& ptr)>& cb) = 0;
 
   virtual void heartbeat(const std::string& target_host, const Metadata& metadata, const HeartbeatRequest& request,
                          std::chrono::milliseconds timeout,
-                         const std::function<void(bool, const HeartbeatResponse&)>& cb) = 0;
+                         const std::function<void(const std::error_code&, const HeartbeatResponse&)>& cb) = 0;
 
   virtual void multiplexingCall(const std::string& target, const Metadata& metadata, const MultiplexingRequest& request,
                                 std::chrono::milliseconds timeout,
@@ -42,10 +44,10 @@
   virtual bool wrapMessage(const rmq::Message& item, MQMessageExt& message_ext) = 0;
 
   virtual void ack(const std::string& target_host, const Metadata& metadata, const AckMessageRequest& request,
-                   std::chrono::milliseconds timeout, const std::function<void(bool)>& cb) = 0;
+                   std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& cb) = 0;
 
   virtual void nack(const std::string& target_host, const Metadata& metadata, const NackMessageRequest& request,
-                    std::chrono::milliseconds timeout, const std::function<void(bool)>& callback) = 0;
+                    std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& callback) = 0;
 
   virtual void forwardMessageToDeadLetterQueue(
       const std::string& target_host, const Metadata& metadata, const ForwardMessageToDeadLetterQueueRequest& request,
@@ -54,22 +56,23 @@
 
   virtual void endTransaction(const std::string& target_host, const Metadata& metadata,
                               const EndTransactionRequest& request, std::chrono::milliseconds timeout,
-                              const std::function<void(bool, const EndTransactionResponse&)>& cb) = 0;
+                              const std::function<void(const std::error_code&, const EndTransactionResponse&)>& cb) = 0;
 
   virtual void queryOffset(const std::string& target_host, const Metadata& metadata, const QueryOffsetRequest& request,
                            std::chrono::milliseconds timeout,
-                           const std::function<void(bool, const QueryOffsetResponse&)>& cb) = 0;
+                           const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) = 0;
 
   virtual void
   healthCheck(const std::string& target_host, const Metadata& metadata, const HealthCheckRequest& request,
               std::chrono::milliseconds timeout,
-              const std::function<void(const std::string&, const InvocationContext<HealthCheckResponse>*)>& cb) = 0;
+              const std::function<void(const std::error_code&, const InvocationContext<HealthCheckResponse>*)>& cb) = 0;
 
   virtual void addClientObserver(std::weak_ptr<Client> client) = 0;
 
-  virtual void queryAssignment(const std::string& target, const Metadata& metadata,
-                               const QueryAssignmentRequest& request, std::chrono::milliseconds timeout,
-                               const std::function<void(bool, const QueryAssignmentResponse&)>& cb) = 0;
+  virtual void
+  queryAssignment(const std::string& target, const Metadata& metadata, const QueryAssignmentRequest& request,
+                  std::chrono::milliseconds timeout,
+                  const std::function<void(const std::error_code&, const QueryAssignmentResponse&)>& cb) = 0;
 
   virtual void receiveMessage(const std::string& target, const Metadata& metadata, const ReceiveMessageRequest& request,
                               std::chrono::milliseconds timeout, const std::shared_ptr<ReceiveMessageCallback>& cb) = 0;
@@ -87,6 +90,8 @@
   virtual bool notifyClientTermination(const std::string& target_host, const Metadata& metadata,
                                        const NotifyClientTerminationRequest& request,
                                        std::chrono::milliseconds timeout) = 0;
+
+  virtual State state() const = 0;
 };
 
 using ClientManagerPtr = std::shared_ptr<ClientManager>;
diff --git a/src/main/cpp/client/include/ClientManagerImpl.h b/src/main/cpp/client/include/ClientManagerImpl.h
index ba56c47..2cb179b 100644
--- a/src/main/cpp/client/include/ClientManagerImpl.h
+++ b/src/main/cpp/client/include/ClientManagerImpl.h
@@ -6,6 +6,7 @@
 #include <functional>
 #include <future>
 #include <string>
+#include <system_error>
 #include <vector>
 
 #include "absl/base/thread_annotations.h"
@@ -65,7 +66,7 @@
    */
   void resolveRoute(const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
                     std::chrono::milliseconds timeout,
-                    const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) override
+                    const std::function<void(const std::error_code&, const TopicRouteDataPtr&)>& cb) override
       LOCKS_EXCLUDED(rpc_clients_mtx_);
 
   void doHealthCheck() LOCKS_EXCLUDED(clients_mtx_);
@@ -74,16 +75,15 @@
    * If inactive RPC clients refer to remote hosts that are absent from topic_route_table_, we need to purge them
    * immediately.
    */
-  void cleanOfflineRpcClients() LOCKS_EXCLUDED(clients_mtx_, rpc_clients_mtx_);
+  std::vector<std::string> cleanOfflineRpcClients() LOCKS_EXCLUDED(clients_mtx_, rpc_clients_mtx_);
 
   /**
    * Execute health-check on behalf of the client.
    */
-  void
-  healthCheck(const std::string& target_host, const Metadata& metadata, const HealthCheckRequest& request,
-              std::chrono::milliseconds timeout,
-              const std::function<void(const std::string&, const InvocationContext<HealthCheckResponse>*)>& cb) override
-      LOCKS_EXCLUDED(rpc_clients_mtx_);
+  void healthCheck(const std::string& target_host, const Metadata& metadata, const HealthCheckRequest& request,
+                   std::chrono::milliseconds timeout,
+                   const std::function<void(const std::error_code&, const InvocationContext<HealthCheckResponse>*)>& cb)
+      override LOCKS_EXCLUDED(rpc_clients_mtx_);
 
   bool send(const std::string& target_host, const Metadata& metadata, SendMessageRequest& request,
             SendCallback* cb) override LOCKS_EXCLUDED(rpc_clients_mtx_);
@@ -114,7 +114,7 @@
 
   void queryAssignment(const std::string& target, const Metadata& metadata, const QueryAssignmentRequest& request,
                        std::chrono::milliseconds timeout,
-                       const std::function<void(bool, const QueryAssignmentResponse&)>& cb) override;
+                       const std::function<void(const std::error_code&, const QueryAssignmentResponse&)>& cb) override;
 
   void receiveMessage(const std::string& target, const Metadata& metadata, const ReceiveMessageRequest& request,
                       std::chrono::milliseconds timeout, const std::shared_ptr<ReceiveMessageCallback>& cb) override
@@ -137,10 +137,10 @@
    * @param request Ack message request.
    */
   void ack(const std::string& target_host, const Metadata& metadata, const AckMessageRequest& request,
-           std::chrono::milliseconds timeout, const std::function<void(bool)>& cb) override;
+           std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& cb) override;
 
   void nack(const std::string& target_host, const Metadata& metadata, const NackMessageRequest& request,
-            std::chrono::milliseconds timeout, const std::function<void(bool)>& callback) override;
+            std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& callback) override;
 
   void forwardMessageToDeadLetterQueue(
       const std::string& target_host, const Metadata& metadata, const ForwardMessageToDeadLetterQueueRequest& request,
@@ -163,7 +163,7 @@
    */
   void endTransaction(const std::string& target_host, const Metadata& metadata, const EndTransactionRequest& request,
                       std::chrono::milliseconds timeout,
-                      const std::function<void(bool, const EndTransactionResponse&)>& cb) override;
+                      const std::function<void(const std::error_code&, const EndTransactionResponse&)>& cb) override;
 
   void multiplexingCall(const std::string& target, const Metadata& metadata, const MultiplexingRequest& request,
                         std::chrono::milliseconds timeout,
@@ -171,7 +171,7 @@
 
   void queryOffset(const std::string& target_host, const Metadata& metadata, const QueryOffsetRequest& request,
                    std::chrono::milliseconds timeout,
-                   const std::function<void(bool, const QueryOffsetResponse&)>& cb) override;
+                   const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) override;
 
   void pullMessage(const std::string& target_host, const Metadata& metadata, const PullMessageRequest& request,
                    std::chrono::milliseconds timeout,
@@ -181,15 +181,19 @@
                                const NotifyClientTerminationRequest& request,
                                std::chrono::milliseconds timeout) override;
 
-  void trace(bool trace) { trace_ = trace; }
+  void trace(bool trace) {
+    trace_ = trace;
+  }
 
   void heartbeat(const std::string& target_host, const Metadata& metadata, const HeartbeatRequest& request,
                  std::chrono::milliseconds timeout,
-                 const std::function<void(bool, const HeartbeatResponse&)>& cb) override;
+                 const std::function<void(const std::error_code&, const HeartbeatResponse&)>& cb) override;
 
   void processPullResult(const grpc::ClientContext& client_context, const PullMessageResponse& response,
                          ReceiveMessageResult& result, const std::string& target_host) override;
 
+  State state() const override;
+
 private:
   void processPopResult(const grpc::ClientContext& client_context, const ReceiveMessageResponse& response,
                         ReceiveMessageResult& result, const std::string& target_host);
diff --git a/src/main/cpp/client/include/ReceiveMessageCallback.h b/src/main/cpp/client/include/ReceiveMessageCallback.h
index 738b8a7..e4088a6 100644
--- a/src/main/cpp/client/include/ReceiveMessageCallback.h
+++ b/src/main/cpp/client/include/ReceiveMessageCallback.h
@@ -2,12 +2,18 @@
 
 #include "ReceiveMessageResult.h"
 #include "rocketmq/AsyncCallback.h"
+#include "rocketmq/ErrorCode.h"
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
+
 class ReceiveMessageCallback : public AsyncCallback {
 public:
-  virtual ~ReceiveMessageCallback() = default;
-  virtual void onSuccess(ReceiveMessageResult& result) = 0;
-  virtual void onException(MQException& e) = 0;
+  ~ReceiveMessageCallback() override = default;
+
+  virtual void onSuccess(ReceiveMessageResult &result) = 0;
+
+  virtual void onFailure(const std::error_code &ec) = 0;
 };
+
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/client/mocks/include/ClientManagerMock.h b/src/main/cpp/client/mocks/include/ClientManagerMock.h
index 7abce6e..f5e5bc2 100644
--- a/src/main/cpp/client/mocks/include/ClientManagerMock.h
+++ b/src/main/cpp/client/mocks/include/ClientManagerMock.h
@@ -3,6 +3,7 @@
 #include "ClientManager.h"
 #include "gmock/gmock.h"
 #include <chrono>
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -18,12 +19,12 @@
 
   MOCK_METHOD(void, resolveRoute,
               (const std::string&, const Metadata&, const QueryRouteRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool, const TopicRouteDataPtr&)>&)),
+               (const std::function<void(const std::error_code&, const TopicRouteDataPtr&)>&)),
               (override));
 
   MOCK_METHOD(void, heartbeat,
               (const std::string&, const Metadata&, const HeartbeatRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool, const HeartbeatResponse&)>&)),
+               (const std::function<void(const std::error_code&, const HeartbeatResponse&)>&)),
               (override));
 
   MOCK_METHOD(void, multiplexingCall,
@@ -35,12 +36,12 @@
 
   MOCK_METHOD(void, ack,
               (const std::string&, const Metadata&, const AckMessageRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool)>&)),
+               (const std::function<void(const std::error_code&)>&)),
               (override));
 
   MOCK_METHOD(void, nack,
               (const std::string&, const Metadata&, const NackMessageRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool)>&)),
+               (const std::function<void(const std::error_code&)>&)),
               (override));
 
   MOCK_METHOD(void, forwardMessageToDeadLetterQueue,
@@ -51,24 +52,24 @@
 
   MOCK_METHOD(void, endTransaction,
               (const std::string&, const Metadata&, const EndTransactionRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool, const EndTransactionResponse&)>&)),
+               (const std::function<void(const std::error_code&, const EndTransactionResponse&)>&)),
               (override));
 
   MOCK_METHOD(void, queryOffset,
               (const std::string&, const Metadata&, const QueryOffsetRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool, const QueryOffsetResponse&)>&)),
+               (const std::function<void(const std::error_code&, const QueryOffsetResponse&)>&)),
               (override));
 
   MOCK_METHOD(void, healthCheck,
               (const std::string&, const Metadata&, const HealthCheckRequest&, std::chrono::milliseconds,
-               (const std::function<void(const std::string&, const InvocationContext<HealthCheckResponse>*)>&)),
+               (const std::function<void(const std::error_code&, const InvocationContext<HealthCheckResponse>*)>&)),
               (override));
 
   MOCK_METHOD(void, addClientObserver, (std::weak_ptr<Client>), (override));
 
   MOCK_METHOD(void, queryAssignment,
               (const std::string& target, const Metadata&, const QueryAssignmentRequest&, std::chrono::milliseconds,
-               (const std::function<void(bool, const QueryAssignmentResponse&)>&)),
+               (const std::function<void(const std::error_code&, const QueryAssignmentResponse&)>&)),
               (override));
 
   MOCK_METHOD(void, receiveMessage,
@@ -90,6 +91,8 @@
   MOCK_METHOD(bool, notifyClientTermination,
               (const std::string&, const Metadata&, const NotifyClientTerminationRequest&, std::chrono::milliseconds),
               (override));
+
+  MOCK_METHOD(State, state, (), (const override));
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/client/mocks/include/ClientMock.h b/src/main/cpp/client/mocks/include/ClientMock.h
index 7ad69b0..dfd7a66 100644
--- a/src/main/cpp/client/mocks/include/ClientMock.h
+++ b/src/main/cpp/client/mocks/include/ClientMock.h
@@ -13,6 +13,8 @@
 
   MOCK_METHOD(bool, active, (), (override));
 
+  MOCK_METHOD(void, onRemoteEndpointRemoval, (const std::vector<std::string>&), (override));
+
   MOCK_METHOD(void, healthCheck, (), (override));
 
   MOCK_METHOD(void, schedule, (const std::string&, const std::function<void()>&, std::chrono::milliseconds),
diff --git a/src/main/cpp/client/mocks/include/ReceiveMessageCallbackMock.h b/src/main/cpp/client/mocks/include/ReceiveMessageCallbackMock.h
index cb7016c..7f40903 100644
--- a/src/main/cpp/client/mocks/include/ReceiveMessageCallbackMock.h
+++ b/src/main/cpp/client/mocks/include/ReceiveMessageCallbackMock.h
@@ -1,14 +1,16 @@
 #pragma once
-#include "ReceiveMessageCallback.h"
+
 #include "gmock/gmock.h"
 
+#include "ReceiveMessageCallback.h"
+
 ROCKETMQ_NAMESPACE_BEGIN
 
 class ReceiveMessageCallbackMock : public ReceiveMessageCallback {
 public:
-  MOCK_METHOD(void, onSuccess, (ReceiveMessageResult&), (override));
+  MOCK_METHOD(void, onSuccess, (ReceiveMessageResult &), (override));
 
-  MOCK_METHOD(void, onException, (MQException&), (override));
+  MOCK_METHOD(void, onFailure, (const std::error_code &), (override));
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/rocketmq/AsyncReceiveMessageCallback.cpp b/src/main/cpp/rocketmq/AsyncReceiveMessageCallback.cpp
index 654b4e6..9bd1d52 100644
--- a/src/main/cpp/rocketmq/AsyncReceiveMessageCallback.cpp
+++ b/src/main/cpp/rocketmq/AsyncReceiveMessageCallback.cpp
@@ -3,6 +3,7 @@
 #include "ConsumeMessageType.h"
 #include "LoggerImpl.h"
 #include "PushConsumer.h"
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -98,10 +99,10 @@
   }
 }
 
-void AsyncReceiveMessageCallback::onException(MQException& e) {
+void AsyncReceiveMessageCallback::onFailure(const std::error_code& ec) {
   auto process_queue_ptr = process_queue_.lock();
   if (process_queue_ptr) {
-    SPDLOG_WARN("pop message error:{}, pop message later. Queue={}", e.what(), process_queue_ptr->simpleName());
+    SPDLOG_WARN("pop message error:{}, pop message later. Queue={}", ec.message(), process_queue_ptr->simpleName());
     // pop message later
     receiveMessageLater();
   }
diff --git a/src/main/cpp/rocketmq/AwaitPullCallback.cpp b/src/main/cpp/rocketmq/AwaitPullCallback.cpp
index 99ca912..6c942a8 100644
--- a/src/main/cpp/rocketmq/AwaitPullCallback.cpp
+++ b/src/main/cpp/rocketmq/AwaitPullCallback.cpp
@@ -2,19 +2,18 @@
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-void AwaitPullCallback::onSuccess(const PullResult& pull_result) {
+void AwaitPullCallback::onSuccess(const PullResult& pull_result) noexcept {
   absl::MutexLock lk(&mtx_);
-  has_failure_ = false;
   completed_ = true;
   // TODO: optimize out messages copy here.
   pull_result_ = pull_result;
   cv_.SignalAll();
 }
 
-void AwaitPullCallback::onException(const MQException& e) {
+void AwaitPullCallback::onFailure(const std::error_code& ec) noexcept {
   absl::MutexLock lk(&mtx_);
-  has_failure_ = true;
   completed_ = true;
+  ec_ = ec;
   cv_.SignalAll();
 }
 
@@ -24,7 +23,7 @@
     while (!completed_) {
       cv_.Wait(&mtx_);
     }
-    return !has_failure_;
+    return !hasFailure();
   }
 }
 
diff --git a/src/main/cpp/rocketmq/ClientImpl.cpp b/src/main/cpp/rocketmq/ClientImpl.cpp
index df1b5ae..60a4049 100644
--- a/src/main/cpp/rocketmq/ClientImpl.cpp
+++ b/src/main/cpp/rocketmq/ClientImpl.cpp
@@ -2,9 +2,11 @@
 #include <chrono>
 #include <cstdint>
 #include <cstdlib>
+#include <functional>
 #include <iterator>
 #include <memory>
 #include <string>
+#include <system_error>
 #include <utility>
 
 #include "absl/strings/str_join.h"
@@ -85,7 +87,8 @@
   }
 }
 
-void ClientImpl::getRouteFor(const std::string& topic, const std::function<void(TopicRouteDataPtr)>& cb) {
+void ClientImpl::getRouteFor(const std::string& topic,
+                             const std::function<void(const std::error_code&, TopicRouteDataPtr)>& cb) {
   TopicRouteDataPtr route = nullptr;
   {
     absl::MutexLock lock(&topic_route_table_mtx_);
@@ -95,7 +98,8 @@
   }
 
   if (route) {
-    cb(route);
+    std::error_code ec;
+    cb(ec, route);
     return;
   }
 
@@ -116,7 +120,7 @@
         SPDLOG_DEBUG("Would reuse prior route request for topic={}", topic);
         return;
       } else {
-        std::vector<std::function<void(const TopicRouteDataPtr&)>> inflight{cb};
+        std::vector<std::function<void(const std::error_code&, const TopicRouteDataPtr&)>> inflight{cb};
         inflight_route_requests_.insert({topic, inflight});
         SPDLOG_INFO("Create inflight route query cache for topic={}", topic);
       }
@@ -124,9 +128,11 @@
   }
 
   if (!query_backend && route) {
-    cb(route);
+    std::error_code ec;
+    cb(ec, route);
   } else {
-    fetchRouteFor(topic, std::bind(&ClientImpl::onTopicRouteReady, this, topic, std::placeholders::_1));
+    fetchRouteFor(topic,
+                  std::bind(&ClientImpl::onTopicRouteReady, this, topic, std::placeholders::_1, std::placeholders::_2));
   }
 }
 
@@ -163,26 +169,27 @@
   }
 }
 
-void ClientImpl::fetchRouteFor(const std::string& topic, const std::function<void(const TopicRouteDataPtr&)>& cb) {
+void ClientImpl::fetchRouteFor(const std::string& topic,
+                               const std::function<void(const std::error_code&, const TopicRouteDataPtr&)>& cb) {
   std::string name_server = name_server_resolver_->current();
   if (name_server.empty()) {
     SPDLOG_WARN("No name server available");
     return;
   }
 
-  auto callback = [this, topic, name_server, cb](bool ok, const TopicRouteDataPtr& route) {
-    if (!ok || !route) {
+  auto callback = [this, topic, name_server, cb](const std::error_code& ec, const TopicRouteDataPtr& route) {
+    if (ec) {
       SPDLOG_WARN("Failed to resolve route for topic={} from {}", topic, name_server);
       std::string name_server_changed = name_server_resolver_->next();
       if (!name_server_changed.empty()) {
         SPDLOG_INFO("Change current name server from {} to {}", name_server, name_server_changed);
       }
-      cb(nullptr);
+      cb(ec, nullptr);
       return;
     }
 
     SPDLOG_DEBUG("Apply callback of fetchRouteFor({}) since a valid route is fetched", topic);
-    cb(route);
+    cb(ec, route);
   };
 
   QueryRouteRequest request;
@@ -212,14 +219,10 @@
 
   if (!topics.empty()) {
     for (const auto& topic : topics) {
-      fetchRouteFor(topic, std::bind(&ClientImpl::updateRouteCache, this, topic, std::placeholders::_1));
+      fetchRouteFor(
+          topic, std::bind(&ClientImpl::updateRouteCache, this, topic, std::placeholders::_1, std::placeholders::_2));
     }
   }
-
-#ifdef ENABLE_TRACING
-  updateTraceProvider();
-#endif
-
   SPDLOG_DEBUG("Topic route info updated");
 }
 
@@ -238,9 +241,9 @@
   Signature::sign(this, metadata);
 
   for (const auto& target : hosts) {
-    auto callback = [target](bool ok, const HeartbeatResponse& response) {
-      if (!ok) {
-        SPDLOG_WARN("Failed to send heartbeat request to {}", target);
+    auto callback = [target](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);
@@ -249,15 +252,16 @@
   }
 }
 
-void ClientImpl::onTopicRouteReady(const std::string& topic, const TopicRouteDataPtr& route) {
+void ClientImpl::onTopicRouteReady(const std::string& topic, const std::error_code& ec,
+                                   const TopicRouteDataPtr& route) {
   if (route) {
     SPDLOG_DEBUG("Received route data for topic={}", topic);
   }
 
-  updateRouteCache(topic, route);
+  updateRouteCache(topic, ec, route);
 
   // Take all pending callbacks
-  std::vector<std::function<void(const TopicRouteDataPtr&)>> pending_requests;
+  std::vector<std::function<void(const std::error_code&, const TopicRouteDataPtr&)>> pending_requests;
   {
     absl::MutexLock lk(&inflight_route_requests_mtx_);
     assert(inflight_route_requests_.contains(topic));
@@ -268,13 +272,13 @@
 
   SPDLOG_DEBUG("Apply cached callbacks with acquired route data for topic={}", topic);
   for (const auto& cb : pending_requests) {
-    cb(route);
+    cb(ec, route);
   }
 }
 
-void ClientImpl::updateRouteCache(const std::string& topic, const TopicRouteDataPtr& route) {
-  if (!route || route->partitions().empty()) {
-    SPDLOG_WARN("Yuck! route for {} is invalid", topic);
+void ClientImpl::updateRouteCache(const std::string& topic, const std::error_code& ec, const TopicRouteDataPtr& route) {
+  if (ec || !route || route->partitions().empty()) {
+    SPDLOG_WARN("Yuck! route for {} is invalid. Cause: {}", topic, ec.message());
     return;
   }
 
@@ -385,6 +389,18 @@
   }
 }
 
+void ClientImpl::onRemoteEndpointRemoval(const std::vector<std::string>& hosts) {
+  absl::MutexLock lk(&isolated_endpoints_mtx_);
+  for (auto it = isolated_endpoints_.begin(); it != isolated_endpoints_.end();) {
+    if (hosts.end() != std::find_if(hosts.begin(), hosts.end(), [&](const std::string& item) { return *it == item; })) {
+      SPDLOG_INFO("Drop isolated-endoint[{}] as it has been removed from route table", *it);
+      isolated_endpoints_.erase(it++);
+    } else {
+      it++;
+    }
+  }
+}
+
 void ClientImpl::healthCheck() {
   std::vector<std::string> endpoints;
   {
@@ -395,14 +411,14 @@
   }
 
   std::weak_ptr<ClientImpl> base(self());
-  auto callback = [base](const std::string& endpoint,
-                         const InvocationContext<HealthCheckResponse>* invocation_context) {
+  auto callback = [base](const std::error_code& ec, const InvocationContext<HealthCheckResponse>* invocation_context) {
     std::shared_ptr<ClientImpl> ptr = base.lock();
-    if (ptr) {
-      ptr->onHealthCheckResponse(endpoint, invocation_context);
-    } else {
+    if (!ptr) {
       SPDLOG_INFO("BaseImpl has been destructed");
+      return;
     }
+
+    ptr->onHealthCheckResponse(ec, invocation_context);
   };
 
   for (const auto& endpoint : endpoints) {
@@ -418,31 +434,16 @@
   client_manager_->getScheduler().schedule(task, task_name, delay, std::chrono::milliseconds(0));
 }
 
-void ClientImpl::onHealthCheckResponse(const std::string& endpoint, const InvocationContext<HealthCheckResponse>* ctx) {
-  if (!ctx) {
-    SPDLOG_WARN("ClientInstance does not have RPC client for {}. It might have been offline and thus cleaned",
-                endpoint);
-    {
-      absl::MutexLock lk(&isolated_endpoints_mtx_);
-      isolated_endpoints_.erase(endpoint);
-    }
+void ClientImpl::onHealthCheckResponse(const std::error_code& ec, const InvocationContext<HealthCheckResponse>* ctx) {
+  if (ec) {
+    SPDLOG_WARN("Health check to server[host={}] failed. Cause: {}", ec.message());
     return;
   }
 
-  assert(endpoint == ctx->remote_address);
-
-  if (ctx->status.ok()) {
-    if (google::rpc::Code::OK == ctx->response.common().status().code()) {
-      SPDLOG_INFO("Health check to server[host={}] passed. Move it back to active node pool", endpoint);
-      absl::MutexLock lk(&isolated_endpoints_mtx_);
-      isolated_endpoints_.erase(endpoint);
-    } else {
-      SPDLOG_INFO("Health check to server[host={}] failed due to application layer reason: {}",
-                  ctx->response.common().DebugString());
-    }
-  } else {
-    SPDLOG_INFO("Health check to server[host={}] failed due to transport layer reason: {}", endpoint,
-                ctx->status.error_message());
+  SPDLOG_INFO("Health check to server[host={}] passed. Remove it from isolated endpoint pool", ctx->remote_address);
+  {
+    absl::MutexLock lk(&isolated_endpoints_mtx_);
+    isolated_endpoints_.erase(ctx->remote_address);
   }
 }
 
diff --git a/src/main/cpp/rocketmq/ConsumeFifoMessageService.cpp b/src/main/cpp/rocketmq/ConsumeFifoMessageService.cpp
index 1049055..4a9ef06 100644
--- a/src/main/cpp/rocketmq/ConsumeFifoMessageService.cpp
+++ b/src/main/cpp/rocketmq/ConsumeFifoMessageService.cpp
@@ -1,16 +1,19 @@
+#include <chrono>
+#include <functional>
+#include <limits>
+#include <system_error>
+
 #include "ConsumeMessageService.h"
 #include "MessageAccessor.h"
 #include "ProcessQueue.h"
 #include "PushConsumerImpl.h"
-#include <chrono>
-#include <functional>
-#include <limits>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
 ConsumeFifoMessageService ::ConsumeFifoMessageService(std::weak_ptr<PushConsumer> consumer, int thread_count,
                                                       MessageListener* message_listener)
-    : ConsumeMessageService(std::move(consumer), thread_count, message_listener) {}
+    : ConsumeMessageService(std::move(consumer), thread_count, message_listener) {
+}
 
 void ConsumeFifoMessageService::start() {
   ConsumeMessageService::start();
@@ -78,7 +81,9 @@
   }
 }
 
-MessageListenerType ConsumeFifoMessageService::messageListenerType() { return MessageListenerType::FIFO; }
+MessageListenerType ConsumeFifoMessageService::messageListenerType() {
+  return MessageListenerType::FIFO;
+}
 
 void ConsumeFifoMessageService::consumeTask(const ProcessQueueWeakPtr& process_queue, MQMessageExt& message) {
   ProcessQueueSharedPtr process_queue_ptr = process_queue.lock();
@@ -145,31 +150,32 @@
   }
 }
 
-void ConsumeFifoMessageService::onAck(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message, bool ok) {
+void ConsumeFifoMessageService::onAck(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message,
+                                      const std::error_code& ec) {
   auto process_queue_ptr = process_queue.lock();
   if (!process_queue_ptr) {
     SPDLOG_WARN("ProcessQueue has destructed.");
     return;
   }
-  if (ok) {
-    SPDLOG_DEBUG("Acknowledge FIFO message[MessageQueue={}, MsgId={}] OK", process_queue_ptr->simpleName(),
-                 message.getMsgId());
-    process_queue_ptr->unbindFifoConsumeTask();
-    signalDispatcher();
-  } else {
-    SPDLOG_WARN("Failed to acknowledge FIFO message[MessageQueue={}, MsgId={}]", process_queue_ptr->simpleName(),
-                message.getMsgId());
+
+  if (ec) {
+    SPDLOG_WARN("Failed to acknowledge FIFO message[MessageQueue={}, MsgId={}]. Cause: {}",
+                process_queue_ptr->simpleName(), message.getMsgId(), ec.message());
     auto consumer = consumer_.lock();
     if (!consumer) {
       SPDLOG_WARN("Consumer instance has destructed");
       return;
     }
-
     auto task = std::bind(&ConsumeFifoMessageService::scheduleAckTask, this, process_queue, message);
     int32_t duration = 100;
     consumer->schedule("Ack-FIFO-Message-On-Failure", task, std::chrono::milliseconds(duration));
     SPDLOG_INFO("Scheduled to ack message[Topic={}, MessageId={}] in {}ms", message.getTopic(), message.getMsgId(),
                 duration);
+  } else {
+    SPDLOG_DEBUG("Acknowledge FIFO message[MessageQueue={}, MsgId={}] OK", process_queue_ptr->simpleName(),
+                 message.getMsgId());
+    process_queue_ptr->unbindFifoConsumeTask();
+    signalDispatcher();
   }
 }
 
diff --git a/src/main/cpp/rocketmq/ConsumeStandardMessageService.cpp b/src/main/cpp/rocketmq/ConsumeStandardMessageService.cpp
index d74274f..e73674e 100644
--- a/src/main/cpp/rocketmq/ConsumeStandardMessageService.cpp
+++ b/src/main/cpp/rocketmq/ConsumeStandardMessageService.cpp
@@ -1,5 +1,6 @@
 #include <limits>
 #include <string>
+#include <system_error>
 #include <utility>
 
 #include "absl/memory/memory.h"
@@ -25,7 +26,8 @@
 
 ConsumeStandardMessageService::ConsumeStandardMessageService(std::weak_ptr<PushConsumer> consumer, int thread_count,
                                                              MessageListener* message_listener_ptr)
-    : ConsumeMessageService(std::move(consumer), thread_count, message_listener_ptr) {}
+    : ConsumeMessageService(std::move(consumer), thread_count, message_listener_ptr) {
+}
 
 void ConsumeStandardMessageService::start() {
   ConsumeMessageService::start();
@@ -88,7 +90,9 @@
   }
 }
 
-MessageListenerType ConsumeStandardMessageService::messageListenerType() { return MessageListenerType::STANDARD; }
+MessageListenerType ConsumeStandardMessageService::messageListenerType() {
+  return MessageListenerType::STANDARD;
+}
 
 void ConsumeStandardMessageService::consumeTask(const ProcessQueueWeakPtr& process_queue,
                                                 const std::vector<MQMessageExt>& msgs) {
@@ -197,12 +201,12 @@
   {
     for (auto& span : spans) {
       switch (status) {
-      case ConsumeMessageResult::SUCCESS:
-        span.SetStatus(opencensus::trace::StatusCode::OK);
-        break;
-      case ConsumeMessageResult::FAILURE:
-        span.SetStatus(opencensus::trace::StatusCode::UNKNOWN);
-        break;
+        case ConsumeMessageResult::SUCCESS:
+          span.SetStatus(opencensus::trace::StatusCode::OK);
+          break;
+        case ConsumeMessageResult::FAILURE:
+          span.SetStatus(opencensus::trace::StatusCode::UNKNOWN);
+          break;
       }
       span.End();
     }
@@ -219,26 +223,26 @@
       process_queue_ptr->release(msg.getBody().size(), msg.getQueueOffset());
 
       if (status == ConsumeMessageResult::SUCCESS) {
-        auto callback = [process_queue_ptr, message_id](bool ok) {
-          if (ok) {
+        auto callback = [process_queue_ptr, message_id](const std::error_code& ec) {
+          if (ec) {
+            SPDLOG_WARN("Failed to acknowledge message[MessageQueue={}, MsgId={}]. Cause: {}",
+                        process_queue_ptr->simpleName(), message_id, ec.message());
+          } else {
             SPDLOG_DEBUG("Acknowledge message[MessageQueue={}, MsgId={}] OK", process_queue_ptr->simpleName(),
                          message_id);
-          } else {
-            SPDLOG_WARN("Failed to acknowledge message[MessageQueue={}, MsgId={}]", process_queue_ptr->simpleName(),
-                        message_id);
           }
         };
         consumer->ack(msg, callback);
       } else {
-        auto callback = [process_queue_ptr, message_id](bool ok) {
-          if (ok) {
-            SPDLOG_DEBUG("Nack message[MessageQueue={}, MsgId={}] OK", process_queue_ptr->simpleName(), message_id);
-          } else {
-            SPDLOG_INFO(
-                "Failed to negative acknowledge message[MessageQueue={}, MsgId={}]. Message will be re-consumed "
-                "after default invisible time",
-                process_queue_ptr->simpleName(), message_id);
+        auto callback = [process_queue_ptr, message_id](const std::error_code& ec) {
+          if (ec) {
+            SPDLOG_WARN("Failed to negative acknowledge message[MessageQueue={}, MsgId={}]. Cause: {} Message will be "
+                        "re-consumed after default invisible time",
+                        process_queue_ptr->simpleName(), message_id, ec.message());
+            return;
           }
+
+          SPDLOG_DEBUG("Nack message[MessageQueue={}, MsgId={}] OK", process_queue_ptr->simpleName(), message_id);
         };
         consumer->nack(msg, callback);
       }
diff --git a/src/main/cpp/rocketmq/DefaultMQProducer.cpp b/src/main/cpp/rocketmq/DefaultMQProducer.cpp
index f72e64f..2780dca 100644
--- a/src/main/cpp/rocketmq/DefaultMQProducer.cpp
+++ b/src/main/cpp/rocketmq/DefaultMQProducer.cpp
@@ -2,6 +2,8 @@
 
 #include <chrono>
 #include <memory>
+#include <system_error>
+#include <utility>
 
 #include "absl/strings/str_split.h"
 
@@ -24,8 +26,14 @@
   return absl::ToChronoMilliseconds(impl_->getIoTimeout());
 }
 
-SendResult DefaultMQProducer::send(const MQMessage& message, const std::string& message_group) {
-  return impl_->send(message, message_group);
+SendResult DefaultMQProducer::send(MQMessage& message, const std::string& message_group) {
+  message.bindMessageGroup(message_group);
+  std::error_code ec;
+  auto&& send_result = impl_->send(message, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+  return std::move(send_result);
 }
 
 void DefaultMQProducer::setSendMsgTimeout(std::chrono::milliseconds timeout) {
@@ -50,43 +58,109 @@
 
 bool DefaultMQProducer::isTracingEnabled() { return impl_->isTracingEnabled(); }
 
-SendResult DefaultMQProducer::send(const rocketmq::MQMessage& message, bool filter_active_broker) {
-  return impl_->send(message);
+SendResult DefaultMQProducer::send(const MQMessage& message, bool filter_active_broker) {
+  std::error_code ec;
+  auto&& send_result = impl_->send(message, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+  return std::move(send_result);
 }
 
-SendResult DefaultMQProducer::send(const MQMessage& msg, const MQMessageQueue& mq) { return impl_->send(msg, mq); }
-
-SendResult DefaultMQProducer::send(const MQMessage& msg, MessageQueueSelector* selector, void* arg) {
-  return impl_->send(msg, selector, arg);
+SendResult DefaultMQProducer::send(const MQMessage& message, std::error_code& ec) noexcept {
+  return impl_->send(message, ec);
 }
 
-SendResult DefaultMQProducer::send(const MQMessage& message, MessageQueueSelector* selector, void* arg, int retry_times,
+SendResult DefaultMQProducer::send(MQMessage& msg, const MQMessageQueue& mq) {
+  msg.bindMessageQueue(mq);
+  std::error_code ec;
+  auto&& send_result = impl_->send(msg, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+  return std::move(send_result);
+}
+
+SendResult DefaultMQProducer::send(MQMessage& msg, MessageQueueSelector* selector, void* arg) {
+  std::error_code ec;
+  auto&& list = impl_->listMessageQueue(msg.getTopic(), ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+
+  auto&& message_queue = selector->select(list, msg, arg);
+  msg.bindMessageQueue(message_queue);
+
+  auto&& send_result = impl_->send(msg, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+  return std::move(send_result);
+}
+
+SendResult DefaultMQProducer::send(MQMessage& message, MessageQueueSelector* selector, void* arg, int retry_times,
                                    bool select_active_broker) {
-  return impl_->send(message, selector, arg, retry_times);
+  return send(message, selector, arg);
 }
 
 void DefaultMQProducer::send(const MQMessage& message, SendCallback* send_callback, bool select_active_broker) {
   impl_->send(message, send_callback);
 }
 
-void DefaultMQProducer::send(const MQMessage& message, const MQMessageQueue& message_queue,
+void DefaultMQProducer::send(MQMessage& message, const MQMessageQueue& message_queue, SendCallback* send_callback) {
+  message.bindMessageQueue(message_queue);
+  impl_->send(message, send_callback);
+}
+
+void DefaultMQProducer::send(MQMessage& message, MessageQueueSelector* selector, void* arg,
                              SendCallback* send_callback) {
-  impl_->send(message, message_queue, send_callback);
+  std::error_code ec;
+
+  // TODO: make querying route async
+  auto&& list = impl_->listMessageQueue(message.getTopic(), ec);
+  if (ec) {
+    send_callback->onFailure(ec);
+  }
+
+  if (list.empty()) {
+    send_callback->onFailure(ErrorCode::ServiceUnavailable);
+  }
+
+  auto&& message_queue = selector->select(list, message, arg);
+  message.bindMessageQueue(message_queue);
+
+  impl_->send(message, send_callback);
 }
 
-void DefaultMQProducer::send(const MQMessage& message, MessageQueueSelector* selector, void* arg,
-                             SendCallback* send_callback) {
-  impl_->send(message, selector, arg, send_callback);
+void DefaultMQProducer::sendOneway(const MQMessage& message, bool select_active_broker) {
+  std::error_code ec;
+  impl_->sendOneway(message, ec);
 }
 
-void DefaultMQProducer::sendOneway(const MQMessage& message, bool select_active_broker) { impl_->sendOneway(message); }
-
-void DefaultMQProducer::sendOneway(const MQMessage& message, const MQMessageQueue& message_queue) {
-  impl_->sendOneway(message, message_queue);
+void DefaultMQProducer::sendOneway(MQMessage& message, const MQMessageQueue& message_queue) {
+  message.bindMessageQueue(message_queue);
+  std::error_code ec;
+  impl_->sendOneway(message, ec);
+  if (ec) {
+    SPDLOG_INFO("Failed to send message in one-way: {}", ec.message());
+  }
 }
 
-void DefaultMQProducer::sendOneway(const MQMessage& message, MessageQueueSelector* selector, void* arg) {
-  impl_->sendOneway(message, selector, arg);
+void DefaultMQProducer::sendOneway(MQMessage& message, MessageQueueSelector* selector, void* arg) {
+  std::error_code ec;
+  auto&& list = impl_->listMessageQueue(message.getTopic(), ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+
+  if (list.empty()) {
+    ec = ErrorCode::ServiceUnavailable;
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+
+  auto&& message_queue = selector->select(list, message, arg);
+  message.bindMessageQueue(message_queue);
+  impl_->sendOneway(message, ec);
 }
 
 void DefaultMQProducer::setLocalTransactionStateChecker(LocalTransactionStateCheckerPtr checker) {
@@ -98,7 +172,12 @@
 int DefaultMQProducer::getMaxAttemptTimes() const { return impl_->maxAttemptTimes(); }
 
 std::vector<MQMessageQueue> DefaultMQProducer::getTopicMessageQueueInfo(const std::string& topic) {
-  return impl_->getTopicMessageQueueInfo(topic);
+  std::error_code ec;
+  auto&& list = impl_->listMessageQueue(topic, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
+  return std::move(list);
 }
 
 void DefaultMQProducer::setUnitName(std::string unit_name) { impl_->setUnitName(std::move(unit_name)); }
@@ -120,7 +199,11 @@
 void DefaultMQProducer::setRegion(const std::string& region) { impl_->region(region); }
 
 TransactionPtr DefaultMQProducer::prepare(MQMessage& message) {
-  auto transaction = impl_->prepare(message);
+  std::error_code ec;
+  auto transaction = impl_->prepare(message, ec);
+  if (ec) {
+    THROW_MQ_EXCEPTION(MQClientException, ec.message(), ec.value());
+  }
   return transaction;
 }
 
diff --git a/src/main/cpp/rocketmq/ProcessQueueImpl.cpp b/src/main/cpp/rocketmq/ProcessQueueImpl.cpp
index f5c455e..da7c97d 100644
--- a/src/main/cpp/rocketmq/ProcessQueueImpl.cpp
+++ b/src/main/cpp/rocketmq/ProcessQueueImpl.cpp
@@ -3,6 +3,7 @@
 #include <atomic>
 #include <chrono>
 #include <memory>
+#include <system_error>
 #include <utility>
 
 #include "ClientManagerImpl.h"
@@ -77,12 +78,12 @@
 
   auto policy = consumer->receiveMessageAction();
   switch (policy) {
-  case ReceiveMessageAction::POLLING:
-    popMessage();
-    break;
-  case ReceiveMessageAction::PULL:
-    pullMessage();
-    break;
+    case ReceiveMessageAction::POLLING:
+      popMessage();
+      break;
+    case ReceiveMessageAction::PULL:
+      pullMessage();
+      break;
   }
 }
 
@@ -115,29 +116,61 @@
   auto timeout = consumer->getLongPollingTimeout();
 
   auto callback = [this](const InvocationContext<PullMessageResponse>* invocation_context) {
-    if (!invocation_context) {
-      MQException e("Transport layer failure", -1, __FILE__, __LINE__);
-      receive_callback_->onException(e);
-      return;
-    }
+    auto status = invocation_context->status;
+    if (status.ok()) {
+      const auto& common = invocation_context->response.common();
 
-    if (invocation_context->status.ok()) {
-      if (google::rpc::Code::OK == invocation_context->response.common().status().code()) {
-        ReceiveMessageResult result;
-        client_manager_->processPullResult(invocation_context->context, invocation_context->response, result,
-                                           invocation_context->remote_address);
-        receive_callback_->onSuccess(result);
-      } else {
-        auto status = invocation_context->response.common().status();
-        MQException e(status.message(), status.code(), __FILE__, __LINE__);
-        receive_callback_->onException(e);
+      switch (common.status().code()) {
+        case google::rpc::Code::OK: {
+          ReceiveMessageResult result;
+          client_manager_->processPullResult(invocation_context->context, invocation_context->response, result,
+                                             invocation_context->remote_address);
+          receive_callback_->onSuccess(result);
+        } break;
+        case google::rpc::Code::PERMISSION_DENIED: {
+          SPDLOG_WARN("PermissionDenied: {}", common.status().message());
+          std::error_code ec = ErrorCode::Forbidden;
+          receive_callback_->onFailure(ec);
+        } break;
+        case google::rpc::Code::UNAUTHENTICATED: {
+          SPDLOG_WARN("Unauthenticated: {}", common.status().message());
+          std::error_code ec = ErrorCode::Unauthorized;
+          receive_callback_->onFailure(ec);
+        } break;
+        case google::rpc::Code::DEADLINE_EXCEEDED: {
+          SPDLOG_WARN("DeadlineExceeded: {}", common.status().message());
+          std::error_code ec = ErrorCode::GatewayTimeout;
+          receive_callback_->onFailure(ec);
+        } break;
+        case google::rpc::Code::INVALID_ARGUMENT: {
+          SPDLOG_WARN("InvalidArgument: {}", common.status().message());
+          std::error_code ec = ErrorCode::BadRequest;
+          receive_callback_->onFailure(ec);
+        } break;
+        case google::rpc::Code::FAILED_PRECONDITION: {
+          SPDLOG_WARN("FailedPrecondition: {}", common.status().message());
+          std::error_code ec = ErrorCode::PreconditionRequired;
+          receive_callback_->onFailure(ec);
+        } break;
+        case google::rpc::Code::INTERNAL: {
+          SPDLOG_WARN("InternalServerError: {}", common.status().message());
+          std::error_code ec = ErrorCode::InternalServerError;
+          receive_callback_->onFailure(ec);
+        } break;
+        default: {
+          SPDLOG_WARN("Unimplemented: Please upgrade to use latest SDK release");
+          std::error_code ec = ErrorCode::NotImplemented;
+          receive_callback_->onFailure(ec);
+        } break;
       }
     } else {
-      MQException e(invocation_context->status.error_message(), invocation_context->status.error_code(), __FILE__,
-                    __LINE__);
-      receive_callback_->onException(e);
+      SPDLOG_WARN("Failed to receive valid gRPC response from server. gRPC-status[code={}, message={}]",
+                  status.error_code(), status.error_message());
+      std::error_code ec = ErrorCode::RequestTimeout;
+      receive_callback_->onFailure(ec);
     }
   };
+
   client_manager_->pullMessage(message_queue_.serviceAddress(), metadata, request, absl::ToChronoMilliseconds(timeout),
                                callback);
 }
@@ -161,13 +194,14 @@
       const std::string& msg_id = message.getMsgId();
       if (!filter_expression_.accept(message)) {
         const std::string& topic = message.getTopic();
-        auto callback = [topic, msg_id](bool ok) {
-          if (ok) {
+        auto callback = [topic, msg_id](const std::error_code& ec) {
+          if (ec) {
+            SPDLOG_WARN(
+                "Failed to ack message[Topic={}, MsgId={}] directly as it fails to pass filter expression. Cause: {}",
+                topic, msg_id, ec.message());
+          } else {
             SPDLOG_DEBUG("Ack message[Topic={}, MsgId={}] directly as it fails to pass filter expression", topic,
                          msg_id);
-          } else {
-            SPDLOG_WARN("Failed to ack message[Topic={}, MsgId={}] directly as it fails to pass filter expression",
-                        topic, msg_id);
           }
         };
         consumer->ack(message, callback);
@@ -248,14 +282,14 @@
   if (optional.has_value()) {
     auto expression = optional.value();
     switch (expression.type_) {
-    case TAG:
-      filter_expression->set_type(rmq::FilterType::TAG);
-      filter_expression->set_expression(expression.content_);
-      break;
-    case SQL92:
-      filter_expression->set_type(rmq::FilterType::SQL);
-      filter_expression->set_expression(expression.content_);
-      break;
+      case TAG:
+        filter_expression->set_type(rmq::FilterType::TAG);
+        filter_expression->set_expression(expression.content_);
+        break;
+      case SQL92:
+        filter_expression->set_type(rmq::FilterType::SQL);
+        filter_expression->set_expression(expression.content_);
+        break;
     }
   } else {
     filter_expression->set_type(rmq::FilterType::TAG);
@@ -306,12 +340,20 @@
   wrapFilterExpression(request.mutable_filter_expression());
 }
 
-std::weak_ptr<PushConsumer> ProcessQueueImpl::getConsumer() { return consumer_; }
+std::weak_ptr<PushConsumer> ProcessQueueImpl::getConsumer() {
+  return consumer_;
+}
 
-std::shared_ptr<ClientManager> ProcessQueueImpl::getClientManager() { return client_manager_; }
+std::shared_ptr<ClientManager> ProcessQueueImpl::getClientManager() {
+  return client_manager_;
+}
 
-MQMessageQueue ProcessQueueImpl::getMQMessageQueue() { return message_queue_; }
+MQMessageQueue ProcessQueueImpl::getMQMessageQueue() {
+  return message_queue_;
+}
 
-const FilterExpression& ProcessQueueImpl::getFilterExpression() const { return filter_expression_; }
+const FilterExpression& ProcessQueueImpl::getFilterExpression() const {
+  return filter_expression_;
+}
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/rocketmq/ProducerImpl.cpp b/src/main/cpp/rocketmq/ProducerImpl.cpp
index 9f174fd..85f5344 100644
--- a/src/main/cpp/rocketmq/ProducerImpl.cpp
+++ b/src/main/cpp/rocketmq/ProducerImpl.cpp
@@ -1,6 +1,9 @@
 #include "ProducerImpl.h"
 
 #include <atomic>
+#include <limits>
+#include <system_error>
+#include <utility>
 
 #include "absl/strings/str_join.h"
 #include "opencensus/trace/propagation/trace_context.h"
@@ -20,7 +23,6 @@
 #include "UniqueIdGenerator.h"
 #include "UtilAll.h"
 #include "rocketmq/ErrorCode.h"
-#include "rocketmq/MQClientException.h"
 #include "rocketmq/MQMessage.h"
 #include "rocketmq/MQMessageQueue.h"
 #include "rocketmq/Transaction.h"
@@ -55,9 +57,9 @@
 
 bool ProducerImpl::isRunning() const { return State::STARTED == state_.load(std::memory_order_relaxed); }
 
-void ProducerImpl::ensureRunning() const {
+void ProducerImpl::ensureRunning(std::error_code& ec) const noexcept {
   if (!isRunning()) {
-    THROW_MQ_EXCEPTION(MQClientException, "Invoke #start() first", ILLEGAL_STATE);
+    ec = ErrorCode::IllegalState;
   }
 }
 
@@ -118,17 +120,51 @@
   return message_id;
 }
 
-SendResult ProducerImpl::send(const MQMessage& message) {
-  ensureRunning();
+SendResult ProducerImpl::send(const MQMessage& message, std::error_code& ec) noexcept {
+  ensureRunning(ec);
+  if (ec) {
+    return {};
+  }
+
   auto topic_publish_info = getPublishInfo(message.getTopic());
   if (!topic_publish_info) {
-    THROW_MQ_EXCEPTION(MQClientException, "No topic route available", NO_TOPIC_ROUTE_INFO);
+    ec = ErrorCode::NotFound;
+    return {};
   }
 
   std::vector<MQMessageQueue> message_queue_list;
-  takeMessageQueuesRoundRobin(topic_publish_info, message_queue_list, max_attempt_times_);
+
+  if (!message.messageGroup().empty() || message.messageQueue()) {
+    auto&& list = listMessageQueue(message.getTopic(), ec);
+    if (ec) {
+      return {};
+    }
+
+    if (list.empty()) {
+      ec = ErrorCode::ServiceUnavailable;
+      return {};
+    }
+
+    if (!message.messageGroup().empty()) {
+      std::size_t hash_code = std::hash<std::string>{}(message.messageGroup());
+      hash_code = hash_code & std::numeric_limits<std::size_t>::max();
+      std::size_t r = hash_code % list.size();
+      message_queue_list.push_back(list[r]);
+    } else {
+      for (const auto& entry : list) {
+        if (entry == message.messageQueue()) {
+          message_queue_list.push_back(entry);
+          break;
+        }
+      }
+    }
+  } else {
+    takeMessageQueuesRoundRobin(topic_publish_info, message_queue_list, max_attempt_times_);
+  }
+
   if (message_queue_list.empty()) {
-    THROW_MQ_EXCEPTION(MQClientException, "No topic route available", NO_TOPIC_ROUTE_INFO);
+    ec = ErrorCode::ServiceUnavailable;
+    return {};
   }
 
   AwaitSendCallback callback;
@@ -138,138 +174,56 @@
   if (callback) {
     return callback.sendResult();
   }
-  THROW_MQ_EXCEPTION(MQClientException, callback.errorMessage(), FAILED_TO_SEND_MESSAGE);
-}
 
-SendResult ProducerImpl::send(const MQMessage& message, const std::string& message_group) {
-  MessageGroupQueueSelector selector(message_group);
-  return send(message, &selector, nullptr);
-}
-
-SendResult ProducerImpl::send(const MQMessage& message, const MQMessageQueue& message_queue) {
-  ensureRunning();
-  std::vector<MQMessageQueue> message_queue_list{withServiceAddress(message_queue)};
-  AwaitSendCallback callback;
-  send0(message, &callback, message_queue_list, max_attempt_times_);
-  callback.await();
-  if (callback) {
-    return callback.sendResult();
-  }
-  THROW_MQ_EXCEPTION(MQClientException, callback.errorMessage(), FAILED_TO_SEND_MESSAGE);
-}
-
-SendResult ProducerImpl::send(const MQMessage& message, MessageQueueSelector* selector, void* arg) {
-  ensureRunning();
-  std::vector<MQMessageQueue> message_queue_list;
-  executeMessageQueueSelector(message, selector, arg, message_queue_list);
-  if (message_queue_list.empty()) {
-    THROW_MQ_EXCEPTION(MQClientException, "No topic route available", NO_TOPIC_ROUTE_INFO);
-  }
-  AwaitSendCallback callback;
-  send0(message, &callback, message_queue_list, max_attempt_times_);
-  callback.await();
-  if (callback) {
-    return callback.sendResult();
-  }
-  THROW_MQ_EXCEPTION(MQClientException, callback.errorMessage(), FAILED_TO_SEND_MESSAGE);
-}
-
-SendResult ProducerImpl::send(const MQMessage& message, MessageQueueSelector* selector, void* arg, int max_attempts) {
-  ensureRunning();
-  std::vector<MQMessageQueue> message_queue_list;
-  executeMessageQueueSelector(message, selector, arg, message_queue_list);
-  if (message_queue_list.empty()) {
-    THROW_MQ_EXCEPTION(MQClientException, "No topic route available", NO_TOPIC_ROUTE_INFO);
-  }
-  AwaitSendCallback callback;
-  send0(message, &callback, message_queue_list, max_attempts);
-  callback.await();
-  if (callback) {
-    return callback.sendResult();
-  }
-  THROW_MQ_EXCEPTION(MQClientException, callback.errorMessage(), FAILED_TO_SEND_MESSAGE);
+  ec = callback.errorCode();
+  return {};
 }
 
 void ProducerImpl::send(const MQMessage& message, SendCallback* cb) {
-  ensureRunning();
-  auto callback = [this, message, cb](const TopicPublishInfoPtr& publish_info) {
-    if (!publish_info) {
-      MQClientException e("Failed to acquire topic route data", NO_TOPIC_ROUTE_INFO, __FILE__, __LINE__);
-      cb->onException(e);
+  std::error_code ec;
+  ensureRunning(ec);
+  if (ec) {
+    cb->onFailure(ec);
+  }
+
+  auto callback = [this, message, cb](const std::error_code& ec, const TopicPublishInfoPtr& publish_info) {
+    if (ec) {
+      cb->onFailure(ec);
       return;
     }
 
     std::vector<MQMessageQueue> message_queue_list;
-    takeMessageQueuesRoundRobin(publish_info, message_queue_list, max_attempt_times_);
+    if (!message.messageGroup().empty() || message.messageQueue()) {
+      auto&& list = publish_info->getMessageQueueList();
+      if (!message.messageGroup().empty()) {
+        std::size_t hash_code = std::hash<std::string>{}(message.messageGroup());
+        hash_code = hash_code & std::numeric_limits<std::size_t>::max();
+        std::size_t r = hash_code % list.size();
+        message_queue_list.push_back(list[r]);
+      } else {
+        for (const auto& entry : list) {
+          if (entry == message.messageQueue()) {
+            message_queue_list.push_back(entry);
+            break;
+          }
+        }
+      }
+    } else {
+      takeMessageQueuesRoundRobin(publish_info, message_queue_list, max_attempt_times_);
+    }
+
+    if (message_queue_list.empty()) {
+      cb->onFailure(ErrorCode::ServiceUnavailable);
+      return;
+    }
+
     send0(message, cb, message_queue_list, max_attempt_times_);
   };
 
   asyncPublishInfo(message.getTopic(), callback);
 }
 
-void ProducerImpl::send(const MQMessage& message, const MQMessageQueue& message_queue, SendCallback* callback) {
-  ensureRunning();
-  std::vector<MQMessageQueue> message_queue_list{withServiceAddress(message_queue)};
-  send0(message, callback, message_queue_list, max_attempt_times_);
-}
-
-void ProducerImpl::send(const MQMessage& message, MessageQueueSelector* selector, void* arg, SendCallback* callback) {
-  ensureRunning();
-
-  auto cb = [this, message, selector, callback, arg](const TopicPublishInfoPtr& ptr) {
-    if (!ptr) {
-      MQClientException e("Failed to acquire topic route", NO_TOPIC_ROUTE_INFO, __FILE__, __LINE__);
-      callback->onException(e);
-      return;
-    }
-
-    MQMessageQueue queue = selector->select(ptr->getMessageQueueList(), message, arg);
-    std::vector<MQMessageQueue> message_queue_list{queue};
-
-    send0(message, callback, message_queue_list, max_attempt_times_);
-  };
-
-  asyncPublishInfo(message.getTopic(), cb);
-}
-
-void ProducerImpl::sendOneway(const MQMessage& message) {
-  ensureRunning();
-  auto callback = [this, message](const TopicPublishInfoPtr& ptr) {
-    if (!ptr) {
-      SPDLOG_WARN("Failed acquire topic publish info for {}", message.getTopic());
-      return;
-    }
-    MQMessageQueue message_queue;
-    absl::flat_hash_set<std::string> isolated;
-    isolatedEndpoints(isolated);
-    ptr->selectOneActiveMessageQueue(isolated, message_queue);
-    std::vector<MQMessageQueue> list{message_queue};
-    send0(message, onewaySendCallback(), list, 1);
-  };
-  asyncPublishInfo(message.getTopic(), callback);
-}
-
-void ProducerImpl::sendOneway(const MQMessage& message, const MQMessageQueue& message_queue) {
-  ensureRunning();
-  std::vector<MQMessageQueue> list{withServiceAddress(message_queue)};
-  send0(message, onewaySendCallback(), list, 1);
-}
-
-void ProducerImpl::sendOneway(const MQMessage& message, MessageQueueSelector* selector, void* arg) {
-  ensureRunning();
-
-  auto callback = [this, message, selector, arg](const TopicPublishInfoPtr& ptr) {
-    if (!ptr) {
-      SPDLOG_WARN("No topic route for {}", message.getTopic());
-      return;
-    }
-    MQMessageQueue queue = selector->select(ptr->getMessageQueueList(), message, arg);
-    std::vector<MQMessageQueue> list{queue};
-    send0(message, onewaySendCallback(), list, 1);
-  };
-
-  asyncPublishInfo(message.getTopic(), callback);
-}
+void ProducerImpl::sendOneway(const MQMessage& message, std::error_code& ec) { send(message, ec); }
 
 void ProducerImpl::setLocalTransactionStateChecker(LocalTransactionStateCheckerPtr checker) {
   transaction_state_checker_ = std::move(checker);
@@ -279,9 +233,8 @@
   const std::string& target = callback->messageQueue().serviceAddress();
   if (target.empty()) {
     SPDLOG_WARN("Failed to resolve broker address from MessageQueue");
-    MQClientException e("Failed to resolve broker address", FAILED_TO_RESOLVE_BROKER_ADDRESS_FROM_TOPIC_ROUTE, __FILE__,
-                        __LINE__);
-    callback->onException(e);
+    std::error_code ec = ErrorCode::BadGateway;
+    callback->onFailure(ec);
     return;
   }
 
@@ -333,20 +286,20 @@
   assert(callback);
 
   if (!validate(message)) {
-    MQClientException e("Message is illegal", MESSAGE_ILLEGAL, __FILE__, __LINE__);
-    callback->onException(e);
+    std::error_code ec = ErrorCode::BadRequest;
+    callback->onFailure(ec);
     return;
   }
 
   if (list.empty()) {
-    MQClientException e("Topic route not found", NO_TOPIC_ROUTE_INFO, __FILE__, __LINE__);
-    callback->onException(e);
+    std::error_code ec = ErrorCode::NotFound;
+    callback->onFailure(ec);
     return;
   }
 
   if (max_attempt_times <= 0) {
-    MQClientException e("Retry times illegal", ERR_INVALID_MAX_ATTEMPT_TIME, __FILE__, __LINE__);
-    callback->onException(e);
+    std::error_code ec = ErrorCode::BadConfiguration;
+    callback->onFailure(ec);
     return;
   }
   MQMessageQueue message_queue = list[0];
@@ -403,26 +356,16 @@
 
   absl::Mutex mtx;
   absl::CondVar cv;
-  auto cb = [&, span](bool rpc_ok, const EndTransactionResponse& response) {
+  auto cb = [&, span](const std::error_code& ec, const EndTransactionResponse& response) {
     completed = true;
-    if (!rpc_ok) {
+    if (ec) {
       {
         span.SetStatus(opencensus::trace::StatusCode::ABORTED);
-        span.AddAnnotation("gRPC tier failure");
+        span.AddAnnotation(ec.message());
         span.End();
       }
-
-      SPDLOG_WARN("Failed to send {} transaction request to {}", action, target);
+      SPDLOG_WARN("Failed to send {} transaction request to {}. Cause: ", action, target, ec.message());
       success = false;
-    } else if (response.common().status().code() != google::rpc::Code::OK) {
-      {
-        span.SetStatus(opencensus::trace::ABORTED);
-        span.AddAnnotation(response.common().status().DebugString());
-        span.End();
-      }
-      success = false;
-      SPDLOG_WARN("Server[host={}] failed to {} transaction. Reason: {}", target, action,
-                  response.common().DebugString());
     } else {
       {
         span.SetStatus(opencensus::trace::StatusCode::OK);
@@ -450,17 +393,21 @@
   endpoints.insert(isolated_endpoints_.begin(), isolated_endpoints_.end());
 }
 
-MQMessageQueue ProducerImpl::withServiceAddress(const MQMessageQueue& message_queue) {
+MQMessageQueue ProducerImpl::withServiceAddress(const MQMessageQueue& message_queue, std::error_code& ec) {
   if (!message_queue.serviceAddress().empty()) {
     return message_queue;
   }
 
   if (message_queue.getTopic().empty() || message_queue.getBrokerName().empty() || message_queue.getQueueId() < 0) {
-    MQClientException e("Message queue is illegal", MESSAGE_QUEUE_ILLEGAL, __FILE__, __LINE__);
-    throw e;
+    ec = ErrorCode::BadRequest;
+    return {};
   }
 
-  std::vector<MQMessageQueue> list = getTopicMessageQueueInfo(message_queue.getTopic());
+  std::vector<MQMessageQueue> list = listMessageQueue(message_queue.getTopic(), ec);
+  if (ec) {
+    return {};
+  }
+
   for (const auto& item : list) {
     if (item == message_queue) {
       return item;
@@ -468,8 +415,8 @@
   }
 
   if (list.empty()) {
-    MQClientException e("No topic route available", NO_TOPIC_ROUTE_INFO, __FILE__, __LINE__);
-    throw e;
+    ec = ErrorCode::NotFound;
+    return {};
   } else {
     return *list.begin();
   }
@@ -485,17 +432,16 @@
   isolated_endpoints_.insert(target);
 }
 
-std::unique_ptr<TransactionImpl> ProducerImpl::prepare(MQMessage& message) {
-  try {
-    message.messageType(MessageType::TRANSACTION);
-    SendResult send_result = send(message);
-    return std::unique_ptr<TransactionImpl>(new TransactionImpl(
-        send_result.getMsgId(), send_result.getTransactionId(), send_result.getMessageQueue().serviceAddress(),
-        send_result.traceContext(), ProducerImpl::shared_from_this()));
-  } catch (const MQClientException& e) {
-    SPDLOG_ERROR("Failed to send transaction message. Cause: {}", e.what());
+std::unique_ptr<TransactionImpl> ProducerImpl::prepare(MQMessage& message, std::error_code& ec) {
+  message.messageType(MessageType::TRANSACTION);
+  SendResult send_result = send(message, ec);
+  if (ec) {
     return nullptr;
   }
+
+  return std::unique_ptr<TransactionImpl>(new TransactionImpl(
+      send_result.getMsgId(), send_result.getTransactionId(), send_result.getMessageQueue().serviceAddress(),
+      send_result.traceContext(), ProducerImpl::shared_from_this()));
 }
 
 bool ProducerImpl::commit(const std::string& message_id, const std::string& transaction_id,
@@ -509,7 +455,7 @@
 }
 
 void ProducerImpl::asyncPublishInfo(const std::string& topic,
-                                    const std::function<void(const TopicPublishInfoPtr&)>& cb) {
+                                    const std::function<void(const std::error_code&, const TopicPublishInfoPtr&)>& cb) {
   TopicPublishInfoPtr ptr;
   {
     absl::MutexLock lock(&topic_publish_info_mtx_);
@@ -517,12 +463,13 @@
       ptr = topic_publish_info_table_.at(topic);
     }
   }
+  std::error_code ec;
   if (ptr) {
-    cb(ptr);
+    cb(ec, ptr);
   } else {
-    auto callback = [this, topic, cb](const TopicRouteDataPtr& route) {
-      if (!route) {
-        cb(nullptr);
+    auto callback = [this, topic, cb](const std::error_code& ec, const TopicRouteDataPtr& route) {
+      if (ec) {
+        cb(ec, nullptr);
         return;
       }
 
@@ -531,8 +478,9 @@
         absl::MutexLock lk(&topic_publish_info_mtx_);
         topic_publish_info_table_.insert_or_assign(topic, publish_info);
       }
-      cb(publish_info);
+      cb(ec, publish_info);
     };
+
     getRouteFor(topic, callback);
   }
 }
@@ -542,9 +490,11 @@
   absl::Mutex mtx;
   absl::CondVar cv;
   TopicPublishInfoPtr topic_publish_info;
-  auto cb = [&](const TopicPublishInfoPtr& ptr) {
+  std::error_code error_code;
+  auto cb = [&](const std::error_code& ec, const TopicPublishInfoPtr& ptr) {
     absl::MutexLock lk(&mtx);
     topic_publish_info = ptr;
+    error_code = ec;
     complete = true;
     cv.SignalAll();
   };
@@ -555,37 +505,11 @@
     absl::MutexLock lk(&mtx);
     cv.Wait(&mtx);
   }
+
+  // TODO: propogate error_code to caller
   return topic_publish_info;
 }
 
-bool ProducerImpl::executeMessageQueueSelector(const MQMessage& message, MessageQueueSelector* selector, void* arg,
-                                               std::vector<MQMessageQueue>& result) {
-  TopicPublishInfoPtr publish_info;
-  absl::Mutex mtx;
-  absl::CondVar cv;
-  bool completed = false;
-  auto callback = [&](const TopicPublishInfoPtr& ptr) {
-    absl::MutexLock lk(&mtx);
-    publish_info = ptr;
-    completed = true;
-    cv.SignalAll();
-  };
-  asyncPublishInfo(message.getTopic(), callback);
-
-  while (!completed) {
-    absl::MutexLock lk(&mtx);
-    cv.Wait(&mtx);
-  }
-
-  if (!publish_info) {
-    THROW_MQ_EXCEPTION(MQClientException, "Failed to acquire topic route data", NO_TOPIC_ROUTE_INFO);
-  }
-
-  MQMessageQueue queue = selector->select(publish_info->getMessageQueueList(), message, arg);
-  result.emplace_back(std::move(queue));
-  return true;
-}
-
 void ProducerImpl::takeMessageQueuesRoundRobin(const TopicPublishInfoPtr& publish_info,
                                                std::vector<MQMessageQueue>& message_queues, int number) {
   assert(publish_info);
@@ -594,17 +518,19 @@
   publish_info->takeMessageQueues(isolated, message_queues, number);
 }
 
-std::vector<MQMessageQueue> ProducerImpl::getTopicMessageQueueInfo(const std::string& topic) {
+std::vector<MQMessageQueue> ProducerImpl::listMessageQueue(const std::string& topic, std::error_code& ec) {
   absl::Mutex mtx;
   absl::CondVar cv;
   bool completed = false;
   TopicPublishInfoPtr ptr;
-  auto await_callback = [&](const TopicPublishInfoPtr& publish_info) {
+  auto await_callback = [&](const std::error_code& error_code, const TopicPublishInfoPtr& publish_info) {
     absl::MutexLock lk(&mtx);
     ptr = publish_info;
+    ec = error_code;
     completed = true;
     cv.SignalAll();
   };
+
   asyncPublishInfo(topic, await_callback);
 
   while (!completed) {
@@ -612,8 +538,8 @@
     cv.Wait(&mtx);
   }
 
-  if (!ptr) {
-    THROW_MQ_EXCEPTION(MQClientException, "Failed to acquire topic publish_info", NO_TOPIC_ROUTE_INFO);
+  if (ec) {
+    return {};
   }
 
   return ptr->getMessageQueueList();
diff --git a/src/main/cpp/rocketmq/PullConsumerImpl.cpp b/src/main/cpp/rocketmq/PullConsumerImpl.cpp
index df45146..daec943 100644
--- a/src/main/cpp/rocketmq/PullConsumerImpl.cpp
+++ b/src/main/cpp/rocketmq/PullConsumerImpl.cpp
@@ -2,8 +2,12 @@
 #include "ClientManagerFactory.h"
 #include "InvocationContext.h"
 #include "Signature.h"
-#include "rocketmq/MessageModel.h"
 #include "apache/rocketmq/v1/definition.pb.h"
+#include "rocketmq/ErrorCode.h"
+#include "rocketmq/MQClientException.h"
+#include "rocketmq/MessageModel.h"
+#include <exception>
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -43,15 +47,21 @@
       return promise->get_future();
     }
   }
-  auto callback = [promise](const TopicRouteDataPtr& route) {
-    if (route) {
-      std::vector<MQMessageQueue> message_queues;
-      for (const auto& partition : route->partitions()) {
-        message_queues.emplace_back(partition.asMessageQueue());
-      }
-      promise->set_value(message_queues);
+
+  auto callback = [promise](const std::error_code& ec, const TopicRouteDataPtr& route) {
+    if (ec) {
+      MQClientException e(ec.message(), ec.value(), __FILE__, __LINE__);
+      promise->set_exception(std::make_exception_ptr(e));
+      return;
     }
+
+    std::vector<MQMessageQueue> message_queues;
+    for (const auto& partition : route->partitions()) {
+      message_queues.emplace_back(partition.asMessageQueue());
+    }
+    promise->set_value(message_queues);
   };
+
   getRouteFor(topic, callback);
   return promise->get_future();
 }
@@ -85,13 +95,13 @@
 
   // TODO: Use std::unique_ptr if C++14 is adopted.
   auto promise_ptr = std::make_shared<std::promise<int64_t>>();
-  auto callback = [promise_ptr](bool ok, const QueryOffsetResponse& response) {
-    if (ok) {
-      promise_ptr->set_value(response.offset());
+  auto callback = [promise_ptr](const std::error_code& ec, const QueryOffsetResponse& response) {
+    if (ec) {
+      MQClientException e(ec.message(), ec.value(), __FILE__, __LINE__);
+      promise_ptr->set_exception(std::make_exception_ptr(e));
       return;
     }
-    MQClientException e("Failed to query offset", -1, __FILE__, __LINE__);
-    promise_ptr->set_exception(std::make_exception_ptr(e));
+    promise_ptr->set_value(response.offset());
   };
 
   client_manager_->queryOffset(query.message_queue.serviceAddress(), metadata, request,
@@ -119,28 +129,63 @@
 
   auto callback = [this, target_host, cb](const InvocationContext<PullMessageResponse>* invocation_context) {
     if (!invocation_context || !invocation_context->status.ok()) {
-      MQClientException exception(fmt::format("Server[{}] is not reachable", target_host), -1, __FILE__, __LINE__);
-      cb->onException(exception);
+      std::error_code ec = ErrorCode::RequestTimeout;
+      cb->onFailure(ec);
       return;
     }
 
     auto response = invocation_context->response;
     auto biz_status = response.common().status();
-    if (google::rpc::Code::OK != biz_status.code()) {
-      MQClientException exception(response.common().status().message(), biz_status.code(), __FILE__, __LINE__);
-      cb->onException(exception);
-      return;
-    }
 
-    std::vector<MQMessageExt> messages;
-    for (const auto& item : response.messages()) {
-      MQMessageExt message_ext;
-      if (client_manager_->wrapMessage(item, message_ext)) {
-        messages.emplace_back(message_ext);
+    switch (biz_status.code()) {
+    case google::rpc::Code::OK: {
+      std::vector<MQMessageExt> messages;
+      for (const auto& item : response.messages()) {
+        MQMessageExt message_ext;
+        if (client_manager_->wrapMessage(item, message_ext)) {
+          messages.emplace_back(message_ext);
+        }
       }
+      PullResult pull_result(response.min_offset(), response.max_offset(), response.next_offset(), std::move(messages));
+      cb->onSuccess(pull_result);
+    } break;
+
+    case google::rpc::Code::PERMISSION_DENIED: {
+      SPDLOG_WARN("PermissionDenied: {}", response.common().status().message());
+      std::error_code ec = ErrorCode::Forbidden;
+      cb->onFailure(ec);
+    } break;
+
+    case google::rpc::Code::UNAUTHENTICATED: {
+      SPDLOG_WARN("Unauthenticated: {}", response.common().status().message());
+      std::error_code ec = ErrorCode::Unauthorized;
+      cb->onFailure(ec);
+    } break;
+
+    case google::rpc::Code::DEADLINE_EXCEEDED: {
+      SPDLOG_WARN("GatewayTimeout: {}", response.common().status().message());
+      std::error_code ec = ErrorCode::GatewayTimeout;
+      cb->onFailure(ec);
+    } break;
+
+    case google::rpc::Code::INVALID_ARGUMENT: {
+      SPDLOG_WARN("BadRequest: {}", response.common().status().message());
+      std::error_code ec = ErrorCode::BadRequest;
+      cb->onFailure(ec);
+    } break;
+
+    case google::rpc::Code::INTERNAL: {
+      SPDLOG_WARN("ServerIntervalError: {}", response.common().status().message());
+      std::error_code ec = ErrorCode::InternalServerError;
+      cb->onFailure(ec);
+    } break;
+
+    default: {
+      SPDLOG_WARN("Unsupported response code. Please upgrade to lastest SDK");
+      std::error_code ec = ErrorCode::NotImplemented;
+      cb->onFailure(ec);
+    } break;
     }
-    PullResult pull_result(response.min_offset(), response.max_offset(), response.next_offset(), std::move(messages));
-    cb->onSuccess(pull_result);
   };
 
   absl::flat_hash_map<std::string, std::string> metadata;
diff --git a/src/main/cpp/rocketmq/PushConsumerImpl.cpp b/src/main/cpp/rocketmq/PushConsumerImpl.cpp
index 99550b1..c0508c1 100644
--- a/src/main/cpp/rocketmq/PushConsumerImpl.cpp
+++ b/src/main/cpp/rocketmq/PushConsumerImpl.cpp
@@ -1,5 +1,12 @@
 #include "PushConsumerImpl.h"
 
+#include <cassert>
+#include <chrono>
+#include <cstdlib>
+#include <system_error>
+
+#include "apache/rocketmq/v1/definition.pb.h"
+
 #include "AsyncReceiveMessageCallback.h"
 #include "ClientManagerFactory.h"
 #include "MessageAccessor.h"
@@ -9,16 +16,15 @@
 #include "Signature.h"
 #include "rocketmq/MQClientException.h"
 #include "rocketmq/MessageModel.h"
-#include <apache/rocketmq/v1/definition.pb.h>
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-PushConsumerImpl::PushConsumerImpl(absl::string_view group_name) : ClientImpl(group_name) {}
+PushConsumerImpl::PushConsumerImpl(absl::string_view group_name) : ClientImpl(group_name) {
+}
 
-PushConsumerImpl::~PushConsumerImpl() { SPDLOG_DEBUG("DefaultMQPushConsumerImpl is destructed"); }
+PushConsumerImpl::~PushConsumerImpl() {
+  SPDLOG_DEBUG("DefaultMQPushConsumerImpl is destructed");
+}
 
 void PushConsumerImpl::start() {
   ClientImpl::start();
@@ -152,11 +158,12 @@
       std::string topic = entry.first;
       const auto& filter_expression = entry.second;
       SPDLOG_DEBUG("Scan assignments for {}", topic);
-      auto callback = [this, topic, filter_expression](const TopicAssignmentPtr& assignments) {
-        if (assignments && !assignments->assignmentList().empty()) {
+      auto callback = [this, topic, filter_expression](const std::error_code& ec,
+                                                       const TopicAssignmentPtr& assignments) {
+        if (ec) {
+          SPDLOG_WARN("Failed to acquire assignments for topic={} from load balancer. Cause: {}", topic, ec.message());
+        } else if (assignments && !assignments->assignmentList().empty()) {
           syncProcessQueue(topic, assignments, filter_expression);
-        } else {
-          SPDLOG_WARN("Failed to acquire assignments for topic={} from load balancer for the first time", topic);
         }
       };
       queryAssignment(topic, callback);
@@ -193,15 +200,15 @@
   request.set_client_id(client_id);
 }
 
-void PushConsumerImpl::queryAssignment(const std::string& topic,
-                                       const std::function<void(const TopicAssignmentPtr&)>& cb) {
+void PushConsumerImpl::queryAssignment(
+    const std::string& topic, const std::function<void(const std::error_code&, const TopicAssignmentPtr&)>& cb) {
 
-  auto callback = [this, topic, cb](const TopicRouteDataPtr& topic_route) {
+  auto callback = [this, topic, cb](const std::error_code& ec, const TopicRouteDataPtr& topic_route) {
     TopicAssignmentPtr topic_assignment;
     if (MessageModel::BROADCASTING == message_model_) {
-      if (!topic_route) {
-        SPDLOG_WARN("Failed to get valid route entries for topic={}", topic);
-        cb(topic_assignment);
+      if (ec) {
+        SPDLOG_WARN("Failed to get valid route entries for topic={}. Cause: {}", topic, ec.message());
+        cb(ec, topic_assignment);
       }
 
       std::vector<Assignment> assignments;
@@ -210,7 +217,7 @@
         assignments.emplace_back(Assignment(partition.asMessageQueue()));
       }
       topic_assignment = std::make_shared<TopicAssignment>(std::move(assignments));
-      cb(topic_assignment);
+      cb(ec, topic_assignment);
       return;
     }
 
@@ -226,16 +233,16 @@
 
     absl::flat_hash_map<std::string, std::string> metadata;
     Signature::sign(this, metadata);
-
-    auto assignment_callback = [this, cb, topic, broker_host](bool ok, const QueryAssignmentResponse& response) {
-      if (ok) {
+    auto assignment_callback = [this, cb, topic, broker_host](const std::error_code& ec,
+                                                              const QueryAssignmentResponse& response) {
+      if (ec) {
+        SPDLOG_WARN("Failed to acquire queue assignment of topic={} from brokerAddress={}", topic, broker_host);
+        cb(ec, nullptr);
+      } else {
         SPDLOG_DEBUG("Query topic assignment OK. Topic={}, group={}, assignment-size={}", topic, group_name_,
                      response.assignments().size());
         SPDLOG_TRACE("Query assignment response for {} is: {}", topic, response.DebugString());
-        cb(std::make_shared<TopicAssignment>(response));
-      } else {
-        SPDLOG_WARN("Failed to acquire queue assignment of topic={} from brokerAddress={}", topic, broker_host);
-        cb(nullptr);
+        cb(ec, std::make_shared<TopicAssignment>(response));
       }
     };
 
@@ -348,42 +355,45 @@
   }
 
   switch (receive_message_policy_) {
-  case ReceiveMessageAction::PULL: {
-    int64_t offset = -1;
-    if (!offset_store_ || !offset_store_->readOffset(message_queue, offset)) {
-      // Query latest offset from server.
-      QueryOffsetRequest request;
-      request.mutable_partition()->mutable_topic()->set_resource_namespace(resource_namespace_);
-      request.mutable_partition()->mutable_topic()->set_name(message_queue.getTopic());
-      request.mutable_partition()->set_id(message_queue.getQueueId());
-      request.mutable_partition()->mutable_broker()->set_name(message_queue.getBrokerName());
-      request.set_policy(rmq::QueryOffsetPolicy::END);
-      absl::flat_hash_map<std::string, std::string> metadata;
-      Signature::sign(this, metadata);
-      auto callback = [broker_host, message_queue, process_queue_ptr](bool ok, const QueryOffsetResponse& response) {
-        if (ok) {
-          assert(response.offset() >= 0);
-          process_queue_ptr->nextOffset(response.offset());
-          process_queue_ptr->receiveMessage();
-        } else {
-          SPDLOG_WARN("Failed to acquire latest offset for partition[{}] from server[host={}]",
-                      message_queue.simpleName(), broker_host);
-        }
-      };
-      client_manager_->queryOffset(broker_host, metadata, request, absl::ToChronoMilliseconds(io_timeout_), callback);
+    case ReceiveMessageAction::PULL: {
+      int64_t offset = -1;
+      if (!offset_store_ || !offset_store_->readOffset(message_queue, offset)) {
+        // Query latest offset from server.
+        QueryOffsetRequest request;
+        request.mutable_partition()->mutable_topic()->set_resource_namespace(resource_namespace_);
+        request.mutable_partition()->mutable_topic()->set_name(message_queue.getTopic());
+        request.mutable_partition()->set_id(message_queue.getQueueId());
+        request.mutable_partition()->mutable_broker()->set_name(message_queue.getBrokerName());
+        request.set_policy(rmq::QueryOffsetPolicy::END);
+        absl::flat_hash_map<std::string, std::string> metadata;
+        Signature::sign(this, metadata);
+        auto callback = [broker_host, message_queue, process_queue_ptr](const std::error_code& ec,
+                                                                        const QueryOffsetResponse& response) {
+          if (ec) {
+            SPDLOG_WARN("Failed to acquire latest offset for partition[{}] from server[host={}]. Cause: {}",
+                        message_queue.simpleName(), broker_host, ec.message());
+          } else {
+            assert(response.offset() >= 0);
+            process_queue_ptr->nextOffset(response.offset());
+            process_queue_ptr->receiveMessage();
+          }
+        };
+        client_manager_->queryOffset(broker_host, metadata, request, absl::ToChronoMilliseconds(io_timeout_), callback);
+      }
+      break;
     }
-    break;
-  }
-  case ReceiveMessageAction::POLLING:
-    process_queue_ptr->receiveMessage();
-    break;
+    case ReceiveMessageAction::POLLING:
+      process_queue_ptr->receiveMessage();
+      break;
   }
   return true;
 }
 
-std::shared_ptr<ConsumeMessageService> PushConsumerImpl::getConsumeMessageService() { return consume_message_service_; }
+std::shared_ptr<ConsumeMessageService> PushConsumerImpl::getConsumeMessageService() {
+  return consume_message_service_;
+}
 
-void PushConsumerImpl::ack(const MQMessageExt& msg, const std::function<void(bool)>& callback) {
+void PushConsumerImpl::ack(const MQMessageExt& msg, const std::function<void(const std::error_code&)>& callback) {
   const std::string& target_host = MessageAccessor::targetEndpoint(msg);
   assert(!target_host.empty());
   SPDLOG_DEBUG("Prepare to send ack to broker. BrokerAddress={}, topic={}, queueId={}, msgId={}", target_host,
@@ -395,7 +405,7 @@
   client_manager_->ack(target_host, metadata, request, absl::ToChronoMilliseconds(io_timeout_), callback);
 }
 
-void PushConsumerImpl::nack(const MQMessageExt& msg, const std::function<void(bool)>& callback) {
+void PushConsumerImpl::nack(const MQMessageExt& msg, const std::function<void(const std::error_code&)>& callback) {
   std::string target_host = MessageAccessor::targetEndpoint(msg);
 
   absl::flat_hash_map<std::string, std::string> metadata;
@@ -452,7 +462,9 @@
   request.set_receipt_handle(msg.receiptHandle());
 }
 
-uint32_t PushConsumerImpl::consumeThreadPoolSize() const { return consume_thread_pool_size_; }
+uint32_t PushConsumerImpl::consumeThreadPoolSize() const {
+  return consume_thread_pool_size_;
+}
 
 void PushConsumerImpl::consumeThreadPoolSize(int thread_pool_size) {
   if (thread_pool_size >= 1) {
@@ -460,7 +472,9 @@
   }
 }
 
-uint32_t PushConsumerImpl::consumeBatchSize() const { return consume_batch_size_; }
+uint32_t PushConsumerImpl::consumeBatchSize() const {
+  return consume_batch_size_;
+}
 
 void PushConsumerImpl::consumeBatchSize(uint32_t consume_batch_size) {
 
@@ -492,15 +506,6 @@
   }
 }
 
-#ifdef ENABLE_TRACING
-nostd::shared_ptr<trace::Tracer> DefaultMQPushConsumerImpl::getTracer() {
-  if (nullptr == client_manager_) {
-    return nostd::shared_ptr<trace::Tracer>(nullptr);
-  }
-  return client_manager_->getTracer();
-}
-#endif
-
 void PushConsumerImpl::iterateProcessQueue(const std::function<void(ProcessQueueSharedPtr)>& callback) {
   absl::MutexLock lock(&process_queue_table_mtx_);
   for (const auto& item : process_queue_table_) {
@@ -527,11 +532,11 @@
   absl::Mutex mtx;
   absl::CondVar cv;
   int acquired = 0;
-  auto callback = [&](const TopicRouteDataPtr& route) {
+  auto callback = [&](const std::error_code& ec, const TopicRouteDataPtr& route) {
     absl::MutexLock lk(&mtx);
     countdown--;
     cv.SignalAll();
-    if (route) {
+    if (!ec) {
       acquired++;
     }
   };
@@ -552,12 +557,12 @@
 
   if (message_listener_) {
     switch (message_listener_->listenerType()) {
-    case MessageListenerType::FIFO:
-      request.set_fifo_flag(true);
-      break;
-    case MessageListenerType::STANDARD:
-      request.set_fifo_flag(false);
-      break;
+      case MessageListenerType::FIFO:
+        request.set_fifo_flag(true);
+        break;
+      case MessageListenerType::STANDARD:
+        request.set_fifo_flag(false);
+        break;
     }
   }
 
@@ -566,14 +571,14 @@
   consumer_data->mutable_group()->set_resource_namespace(resource_namespace_);
 
   switch (message_model_) {
-  case MessageModel::BROADCASTING:
-    consumer_data->set_consume_model(rmq::ConsumeModel::BROADCASTING);
-    break;
-  case MessageModel::CLUSTERING:
-    consumer_data->set_consume_model(rmq::ConsumeModel::CLUSTERING);
-    break;
-  default:
-    break;
+    case MessageModel::BROADCASTING:
+      consumer_data->set_consume_model(rmq::ConsumeModel::BROADCASTING);
+      break;
+    case MessageModel::CLUSTERING:
+      consumer_data->set_consume_model(rmq::ConsumeModel::CLUSTERING);
+      break;
+    default:
+      break;
   }
 
   auto subscriptions = consumer_data->mutable_subscriptions();
@@ -586,12 +591,12 @@
       subscription->mutable_topic()->set_name(entry.first);
       subscription->mutable_expression()->set_expression(entry.second.content_);
       switch (entry.second.type_) {
-      case ExpressionType::TAG:
-        subscription->mutable_expression()->set_type(rmq::FilterType::TAG);
-        break;
-      case ExpressionType::SQL92:
-        subscription->mutable_expression()->set_type(rmq::FilterType::SQL);
-        break;
+        case ExpressionType::TAG:
+          subscription->mutable_expression()->set_type(rmq::FilterType::TAG);
+          break;
+        case ExpressionType::SQL92:
+          subscription->mutable_expression()->set_type(rmq::FilterType::SQL);
+          break;
       }
       subscriptions->AddAllocated(subscription);
     }
diff --git a/src/main/cpp/rocketmq/SendCallbacks.cpp b/src/main/cpp/rocketmq/SendCallbacks.cpp
index 8b067ca..ae680e3 100644
--- a/src/main/cpp/rocketmq/SendCallbacks.cpp
+++ b/src/main/cpp/rocketmq/SendCallbacks.cpp
@@ -10,11 +10,11 @@
 
 ROCKETMQ_NAMESPACE_BEGIN
 
-void OnewaySendCallback::onException(const MQException& e) {
-  SPDLOG_WARN("Failed to one-way send message. Message: {}", e.what());
+void OnewaySendCallback::onFailure(const std::error_code& ec) noexcept {
+  SPDLOG_WARN("Failed to one-way send message. Message: {}", ec.message());
 }
 
-void OnewaySendCallback::onSuccess(SendResult& send_result) {
+void OnewaySendCallback::onSuccess(SendResult& send_result) noexcept {
   SPDLOG_DEBUG("Send message in one-way OK. MessageId: {}", send_result.getMsgId());
 }
 
@@ -30,23 +30,21 @@
   }
 }
 
-void AwaitSendCallback::onSuccess(SendResult& send_result) {
+void AwaitSendCallback::onSuccess(SendResult& send_result) noexcept {
   send_result_ = send_result;
-  success_ = true;
   completed_ = true;
   absl::MutexLock lk(&mtx_);
   cv_.SignalAll();
 }
 
-void AwaitSendCallback::onException(const MQException& e) {
-  success_ = false;
-  error_message_ = e.what();
+void AwaitSendCallback::onFailure(const std::error_code& ec) noexcept {
   completed_ = true;
+  ec_ = ec;
   absl::MutexLock lk(&mtx_);
   cv_.SignalAll();
 }
 
-void RetrySendCallback::onSuccess(SendResult& send_result) {
+void RetrySendCallback::onSuccess(SendResult& send_result) noexcept {
   {
     // Mark end of send-message span.
     span_.SetStatus(opencensus::trace::StatusCode::OK);
@@ -58,7 +56,7 @@
   delete this;
 }
 
-void RetrySendCallback::onException(const MQException& e) {
+void RetrySendCallback::onFailure(const std::error_code& ec) noexcept {
   {
     // Mark end of the send-message span.
     span_.SetStatus(opencensus::trace::StatusCode::INTERNAL);
@@ -67,7 +65,7 @@
 
   if (++attempt_times_ >= max_attempt_times_) {
     SPDLOG_WARN("Retried {} times, which exceeds the limit: {}", attempt_times_, max_attempt_times_);
-    callback_->onException(e);
+    callback_->onFailure(ec);
     delete this;
     return;
   }
@@ -75,14 +73,14 @@
   std::shared_ptr<ProducerImpl> producer = producer_.lock();
   if (!producer) {
     SPDLOG_WARN("Producer has been destructed");
-    callback_->onException(e);
+    callback_->onFailure(ec);
     delete this;
     return;
   }
 
   if (candidates_.empty()) {
     SPDLOG_WARN("No alternative hosts to perform additional retries");
-    callback_->onException(e);
+    callback_->onFailure(ec);
     delete this;
     return;
   }
diff --git a/src/main/cpp/rocketmq/include/AsyncReceiveMessageCallback.h b/src/main/cpp/rocketmq/include/AsyncReceiveMessageCallback.h
index 0c415c3..597819a 100644
--- a/src/main/cpp/rocketmq/include/AsyncReceiveMessageCallback.h
+++ b/src/main/cpp/rocketmq/include/AsyncReceiveMessageCallback.h
@@ -2,6 +2,7 @@
 
 #include "ProcessQueue.h"
 #include "ReceiveMessageCallback.h"
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -14,7 +15,7 @@
 
   void onSuccess(ReceiveMessageResult& result) override;
 
-  void onException(MQException& e) override;
+  void onFailure(const std::error_code& ec) override;
 
   void receiveMessageLater();
 
diff --git a/src/main/cpp/rocketmq/include/AwaitPullCallback.h b/src/main/cpp/rocketmq/include/AwaitPullCallback.h
index 95d9572..971bb6e 100644
--- a/src/main/cpp/rocketmq/include/AwaitPullCallback.h
+++ b/src/main/cpp/rocketmq/include/AwaitPullCallback.h
@@ -1,3 +1,7 @@
+#pragma once
+
+#include <system_error>
+
 #include "absl/synchronization/mutex.h"
 
 #include "rocketmq/AsyncCallback.h"
@@ -8,22 +12,24 @@
 public:
   explicit AwaitPullCallback(PullResult& pull_result) : pull_result_(pull_result) {}
 
-  void onSuccess(const PullResult& pull_result) override;
+  void onSuccess(const PullResult& pull_result) noexcept override;
 
-  void onException(const MQException& e) override;
+  void onFailure(const std::error_code& ec) noexcept override;
 
   bool await();
 
-  bool hasFailure() const { return has_failure_; }
+  bool hasFailure() const { return ec_.operator bool(); }
 
   bool isCompleted() const { return completed_; }
 
+  const std::error_code& errorCode() const noexcept { return ec_; }
+
 private:
   PullResult& pull_result_;
   absl::Mutex mtx_;
   absl::CondVar cv_;
   bool completed_{false};
-  bool has_failure_{false};
+  std::error_code ec_;
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/rocketmq/include/ClientImpl.h b/src/main/cpp/rocketmq/include/ClientImpl.h
index 40a9756..342bdcc 100644
--- a/src/main/cpp/rocketmq/include/ClientImpl.h
+++ b/src/main/cpp/rocketmq/include/ClientImpl.h
@@ -2,6 +2,7 @@
 
 #include <chrono>
 #include <cstdint>
+#include <system_error>
 
 #include "absl/strings/string_view.h"
 #include "apache/rocketmq/v1/definition.pb.h"
@@ -28,11 +29,12 @@
 
   virtual void shutdown();
 
-  void getRouteFor(const std::string& topic, const std::function<void(TopicRouteDataPtr)>& cb)
+  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_);
 
   /**
-   * Gather collection of endpoints that are reachable from latest topic route table.
+   * Gather collection of endpoints that are reachable from latest topic route
+   * table.
    *
    * @param endpoints
    */
@@ -45,7 +47,9 @@
     return State::STARTING == state || State::STARTED == state;
   }
 
-  void healthCheck() LOCKS_EXCLUDED(isolated_endpoints_mtx_) override;
+  void onRemoteEndpointRemoval(const std::vector<std::string>& hosts) override LOCKS_EXCLUDED(isolated_endpoints_mtx_);
+
+  void healthCheck() override LOCKS_EXCLUDED(isolated_endpoints_mtx_);
 
   void schedule(const std::string& task_name, const std::function<void(void)>& task,
                 std::chrono::milliseconds delay) override;
@@ -62,7 +66,7 @@
   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_
 
-  absl::flat_hash_map<std::string, std::vector<std::function<void(const TopicRouteDataPtr&)>>>
+  absl::flat_hash_map<std::string, std::vector<std::function<void(const std::error_code&, const TopicRouteDataPtr&)>>>
       inflight_route_requests_ GUARDED_BY(inflight_route_requests_mtx_);
   absl::Mutex inflight_route_requests_mtx_ ACQUIRED_BEFORE(topic_route_table_mtx_); // Protects inflight_route_requests_
   static const char* UPDATE_ROUTE_TASK_NAME;
@@ -91,7 +95,8 @@
   virtual void resolveOrphanedTransactionalMessage(const std::string& transaction_id, const MQMessageExt& message) {}
 
   /**
-   * Concrete publisher/subscriber client is expected to fill other type-specific resources.
+   * Concrete publisher/subscriber client is expected to fill other
+   * type-specific resources.
    */
   virtual ClientResourceBundle resourceBundle() {
     ClientResourceBundle resource_bundle;
@@ -106,36 +111,38 @@
 
 private:
   /**
-   * This is a low-level API that fetches route data from name server through gRPC unary request/response. Once
-   * request/response is completed, either timeout or response arrival in time, callback would get invoked.
+   * This is a low-level API that fetches route data from name server through
+   * gRPC unary request/response. Once request/response is completed, either
+   * timeout or response arrival in time, callback would get invoked.
    * @param topic
    * @param cb
    */
-  void fetchRouteFor(const std::string& topic, const std::function<void(const TopicRouteDataPtr&)>& cb);
+  void fetchRouteFor(const std::string& topic,
+                     const std::function<void(const std::error_code&, const TopicRouteDataPtr&)>& cb);
 
   /**
    * Callback to execute once route data is fetched from name server.
    * @param topic
    * @param route
    */
-  void onTopicRouteReady(const std::string& topic, const TopicRouteDataPtr& route)
+  void onTopicRouteReady(const std::string& topic, const std::error_code& ec, const TopicRouteDataPtr& route)
       LOCKS_EXCLUDED(inflight_route_requests_mtx_);
 
   /**
-   * Update local cache for the topic. Note, route differences are logged in INFO level since route bears fundamental
-   * importance.
+   * Update local cache for the topic. Note, route differences are logged in
+   * INFO level since route bears fundamental importance.
    *
    * @param topic
    * @param route
    */
-  void updateRouteCache(const std::string& topic, const TopicRouteDataPtr& route)
+  void updateRouteCache(const std::string& topic, const std::error_code& ec, const TopicRouteDataPtr& route)
       LOCKS_EXCLUDED(topic_route_table_mtx_);
 
   void multiplexing(const std::string& target, const MultiplexingRequest& request);
 
   void onMultiplexingResponse(const InvocationContext<MultiplexingResponse>* ctx);
 
-  void onHealthCheckResponse(const std::string& endpoint, const InvocationContext<HealthCheckResponse>* ctx)
+  void onHealthCheckResponse(const std::error_code& endpoint, const InvocationContext<HealthCheckResponse>* ctx)
       LOCKS_EXCLUDED(isolated_endpoints_mtx_);
 
   void fillGenericPollingRequest(MultiplexingRequest& request);
diff --git a/src/main/cpp/rocketmq/include/ConsumeMessageService.h b/src/main/cpp/rocketmq/include/ConsumeMessageService.h
index a205705..de4e62f 100644
--- a/src/main/cpp/rocketmq/include/ConsumeMessageService.h
+++ b/src/main/cpp/rocketmq/include/ConsumeMessageService.h
@@ -3,6 +3,7 @@
 #include <memory>
 #include <mutex>
 #include <string>
+#include <system_error>
 
 #include "ProcessQueue.h"
 #include "RateLimiter.h"
@@ -123,7 +124,7 @@
 
   void scheduleAckTask(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message);
 
-  void onAck(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message, bool ok);
+  void onAck(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message, const std::error_code& ec);
 
   void scheduleConsumeTask(const ProcessQueueWeakPtr& process_queue, const MQMessageExt& message);
 
diff --git a/src/main/cpp/rocketmq/include/ProducerImpl.h b/src/main/cpp/rocketmq/include/ProducerImpl.h
index 43459c2..263b11f 100644
--- a/src/main/cpp/rocketmq/include/ProducerImpl.h
+++ b/src/main/cpp/rocketmq/include/ProducerImpl.h
@@ -4,6 +4,7 @@
 #include <memory>
 #include <mutex>
 #include <string>
+#include <system_error>
 
 #include "absl/strings/string_view.h"
 
@@ -35,24 +36,15 @@
 
   void shutdown() override;
 
-  SendResult send(const MQMessage& message);
-  SendResult send(const MQMessage& message, const std::string& message_group);
-  SendResult send(const MQMessage& message, const MQMessageQueue& message_queue);
-  SendResult send(const MQMessage& message, MessageQueueSelector* selector, void* arg);
-
-  SendResult send(const MQMessage& message, MessageQueueSelector* selector, void* arg, int max_attempts);
+  SendResult send(const MQMessage& message, std::error_code& ec) noexcept;
 
   void send(const MQMessage& message, SendCallback* callback);
-  void send(const MQMessage& message, const MQMessageQueue& message_queue, SendCallback* callback);
-  void send(const MQMessage& message, MessageQueueSelector* selector, void* arg, SendCallback* callback);
 
-  void sendOneway(const MQMessage& message);
-  void sendOneway(const MQMessage& message, const MQMessageQueue& message_queue);
-  void sendOneway(const MQMessage& message, MessageQueueSelector* selector, void* arg);
+  void sendOneway(const MQMessage& message, std::error_code& ec);
 
   void setLocalTransactionStateChecker(LocalTransactionStateCheckerPtr checker);
 
-  std::unique_ptr<TransactionImpl> prepare(MQMessage& message);
+  std::unique_ptr<TransactionImpl> prepare(MQMessage& message, std::error_code& ec);
 
   bool commit(const std::string& message_id, const std::string& transaction_id, const std::string& trace_context,
               const std::string& target);
@@ -81,7 +73,7 @@
 
   void setFailedTimes(int times) { failed_times_ = times; }
 
-  std::vector<MQMessageQueue> getTopicMessageQueueInfo(const std::string& topic);
+  std::vector<MQMessageQueue> listMessageQueue(const std::string& topic, std::error_code& ec);
 
   uint32_t compressBodyThreshold() const { return compress_body_threshold_; }
 
@@ -112,14 +104,12 @@
 
   LocalTransactionStateCheckerPtr transaction_state_checker_;
 
-  void asyncPublishInfo(const std::string& topic, const std::function<void(const TopicPublishInfoPtr&)>& cb)
+  void asyncPublishInfo(const std::string& topic,
+                        const std::function<void(const std::error_code&, const TopicPublishInfoPtr&)>& cb)
       LOCKS_EXCLUDED(topic_publish_info_mtx_);
 
   TopicPublishInfoPtr getPublishInfo(const std::string& topic);
 
-  bool executeMessageQueueSelector(const MQMessage& message, MessageQueueSelector* selector, void* arg,
-                                   std::vector<MQMessageQueue>& result);
-
   void takeMessageQueuesRoundRobin(const TopicPublishInfoPtr& publish_info, std::vector<MQMessageQueue>& message_queues,
                                    int number);
 
@@ -128,7 +118,7 @@
 
   bool isRunning() const;
 
-  void ensureRunning() const;
+  void ensureRunning(std::error_code& ec) const noexcept;
 
   bool validate(const MQMessage& message);
 
@@ -139,7 +129,7 @@
 
   void isolatedEndpoints(absl::flat_hash_set<std::string>& endpoints) LOCKS_EXCLUDED(isolated_endpoints_mtx_);
 
-  MQMessageQueue withServiceAddress(const MQMessageQueue& message_queue);
+  MQMessageQueue withServiceAddress(const MQMessageQueue& message_queue, std::error_code& ec);
 };
 
 ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/rocketmq/include/PushConsumer.h b/src/main/cpp/rocketmq/include/PushConsumer.h
index c71bd1e..4842727 100644
--- a/src/main/cpp/rocketmq/include/PushConsumer.h
+++ b/src/main/cpp/rocketmq/include/PushConsumer.h
@@ -2,6 +2,7 @@
 
 #include <functional>
 #include <memory>
+#include <system_error>
 
 #include "ConsumeMessageService.h"
 #include "Consumer.h"
@@ -20,7 +21,7 @@
 
   virtual MessageModel messageModel() const = 0;
 
-  virtual void ack(const MQMessageExt& msg, const std::function<void(bool)>& callback) = 0;
+  virtual void ack(const MQMessageExt& msg, const std::function<void(const std::error_code&)>& callback) = 0;
 
   virtual void forwardToDeadLetterQueue(const MQMessageExt& message, const std::function<void(bool)>& cb) = 0;
 
@@ -32,7 +33,7 @@
 
   virtual void updateOffset(const MQMessageQueue& message_queue, int64_t offset) = 0;
 
-  virtual void nack(const MQMessageExt& message, const std::function<void(bool)>& callback) = 0;
+  virtual void nack(const MQMessageExt& message, const std::function<void(const std::error_code&)>& callback) = 0;
 
   virtual std::shared_ptr<ConsumeMessageService> getConsumeMessageService() = 0;
 
diff --git a/src/main/cpp/rocketmq/include/PushConsumerImpl.h b/src/main/cpp/rocketmq/include/PushConsumerImpl.h
index c03653b..7499593 100644
--- a/src/main/cpp/rocketmq/include/PushConsumerImpl.h
+++ b/src/main/cpp/rocketmq/include/PushConsumerImpl.h
@@ -3,6 +3,7 @@
 #include <memory>
 #include <mutex>
 #include <string>
+#include <system_error>
 
 #include "absl/strings/string_view.h"
 
@@ -72,7 +73,8 @@
    * @param topic Topic to query
    * @return shared pointer to topic assignment info
    */
-  void queryAssignment(const std::string& topic, const std::function<void(const TopicAssignmentPtr&)>& cb);
+  void queryAssignment(const std::string& topic,
+                       const std::function<void(const std::error_code&, const TopicAssignmentPtr&)>& cb);
 
   void syncProcessQueue(const std::string& topic, const TopicAssignmentPtr& topic_assignment,
                         const FilterExpression& filter_expression) LOCKS_EXCLUDED(process_queue_table_mtx_);
@@ -98,7 +100,7 @@
 
   std::shared_ptr<ConsumeMessageService> getConsumeMessageService() override;
 
-  void ack(const MQMessageExt& msg, const std::function<void(bool)>& callback) override;
+  void ack(const MQMessageExt& msg, const std::function<void(const std::error_code&)>& callback) override;
 
   /**
    * Negative acknowledge the given message; Refer to
@@ -109,7 +111,7 @@
    *
    * @param message Message to negate on the broker side.
    */
-  void nack(const MQMessageExt& message, const std::function<void(bool)>& callback) override;
+  void nack(const MQMessageExt& message, const std::function<void(const std::error_code&)>& callback) override;
 
   void forwardToDeadLetterQueue(const MQMessageExt& message, const std::function<void(bool)>& cb) override;
 
diff --git a/src/main/cpp/rocketmq/include/SendCallbacks.h b/src/main/cpp/rocketmq/include/SendCallbacks.h
index bdcc928..3063f93 100644
--- a/src/main/cpp/rocketmq/include/SendCallbacks.h
+++ b/src/main/cpp/rocketmq/include/SendCallbacks.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <memory>
+#include <system_error>
 
 #include "absl/container/flat_hash_map.h"
 #include "absl/synchronization/mutex.h"
@@ -9,6 +10,7 @@
 
 #include "TransactionImpl.h"
 #include "rocketmq/AsyncCallback.h"
+#include "rocketmq/ErrorCode.h"
 #include "rocketmq/MQMessage.h"
 #include "rocketmq/MQMessageQueue.h"
 
@@ -18,69 +20,65 @@
 
 class OnewaySendCallback : public SendCallback {
 public:
-  void onSuccess(SendResult &send_result) override;
+  void onSuccess(SendResult& send_result) noexcept override;
 
-  void onException(const MQException &e) override;
+  void onFailure(const std::error_code& ec) noexcept override;
 };
 
-OnewaySendCallback *onewaySendCallback();
+OnewaySendCallback* onewaySendCallback();
 
 class AwaitSendCallback : public SendCallback {
 public:
-  void onSuccess(SendResult &send_result) override;
+  void onSuccess(SendResult& send_result) noexcept override;
 
-  void onException(const MQException &e) override;
+  void onFailure(const std::error_code& ec) noexcept override;
 
   void await();
 
-  explicit operator bool() const { return success_; }
+  explicit operator bool() const { return !ec_.operator bool(); }
 
-  const SendResult &sendResult() const { return send_result_; }
+  const SendResult& sendResult() const { return send_result_; }
 
-  const std::string &errorMessage() const { return error_message_; }
+  const std::error_code& errorCode() const { return ec_; }
 
 private:
   absl::Mutex mtx_;
   absl::CondVar cv_;
   bool completed_{false};
-  bool success_{false};
   SendResult send_result_;
-  std::string error_message_;
+  std::error_code ec_;
 };
 
 class ProducerImpl;
 
 class RetrySendCallback : public SendCallback {
 public:
-  RetrySendCallback(std::weak_ptr<ProducerImpl> producer, MQMessage message,
-                    int max_attempt_times, SendCallback *callback,
-                    std::vector<MQMessageQueue> candidates)
-      : producer_(std::move(producer)), message_(std::move(message)),
-        max_attempt_times_(max_attempt_times), callback_(callback),
-        candidates_(std::move(candidates)),
-        span_(opencensus::trace::Span::BlankSpan()) {}
+  RetrySendCallback(std::weak_ptr<ProducerImpl> producer, MQMessage message, int max_attempt_times,
+                    SendCallback* callback, std::vector<MQMessageQueue> candidates)
+      : producer_(std::move(producer)), message_(std::move(message)), max_attempt_times_(max_attempt_times),
+        callback_(callback), candidates_(std::move(candidates)), span_(opencensus::trace::Span::BlankSpan()) {}
 
-  void onSuccess(SendResult &send_result) override;
+  void onSuccess(SendResult& send_result) noexcept override;
 
-  void onException(const MQException &e) override;
+  void onFailure(const std::error_code& ec) noexcept override;
 
-  MQMessage &message() { return message_; }
+  MQMessage& message() { return message_; }
 
   int attemptTime() const { return attempt_times_; }
 
-  const MQMessageQueue &messageQueue() const {
+  const MQMessageQueue& messageQueue() const {
     int index = attempt_times_ % candidates_.size();
     return candidates_[index];
   }
 
-  opencensus::trace::Span &span() { return span_; }
+  opencensus::trace::Span& span() { return span_; }
 
 private:
   std::weak_ptr<ProducerImpl> producer_;
   MQMessage message_;
   int attempt_times_{0};
   int max_attempt_times_;
-  SendCallback *callback_{nullptr};
+  SendCallback* callback_{nullptr};
 
   /**
    * @brief Once the first publish attempt failed, the following routable
diff --git a/src/main/cpp/rocketmq/mocks/include/PushConsumerMock.h b/src/main/cpp/rocketmq/mocks/include/PushConsumerMock.h
index feb9367..059577d 100644
--- a/src/main/cpp/rocketmq/mocks/include/PushConsumerMock.h
+++ b/src/main/cpp/rocketmq/mocks/include/PushConsumerMock.h
@@ -2,6 +2,7 @@
 
 #include "ConsumerMock.h"
 #include "PushConsumer.h"
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -11,7 +12,7 @@
 
   MOCK_METHOD(MessageModel, messageModel, (), (const override));
 
-  MOCK_METHOD(void, ack, (const MQMessageExt&, const std::function<void(bool)>&), (override));
+  MOCK_METHOD(void, ack, (const MQMessageExt&, const std::function<void(const std::error_code&)>&), (override));
 
   MOCK_METHOD(void, forwardToDeadLetterQueue, (const MQMessageExt&, const std::function<void(bool)>&), (override));
 
@@ -23,7 +24,7 @@
 
   MOCK_METHOD(void, updateOffset, (const MQMessageQueue&, int64_t), (override));
 
-  MOCK_METHOD(void, nack, (const MQMessageExt&, const std::function<void(bool)>&), (override));
+  MOCK_METHOD(void, nack, (const MQMessageExt&, const std::function<void(const std::error_code&)>&), (override));
 
   MOCK_METHOD(std::shared_ptr<ConsumeMessageService>, getConsumeMessageService, (), (override));
 
diff --git a/src/test/cpp/ut/api/BUILD.bazel b/src/test/cpp/ut/api/BUILD.bazel
new file mode 100644
index 0000000..eda5f4b
--- /dev/null
+++ b/src/test/cpp/ut/api/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@rules_cc//cc:defs.bzl", "cc_test")
+
+cc_test(
+    name = "error_code_test",
+    srcs = [
+        "ErrorCodeTest.cpp",
+    ],
+    deps = [
+        "//src/main/cpp/base:base_library",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
\ No newline at end of file
diff --git a/src/test/cpp/ut/api/ErrorCodeTest.cpp b/src/test/cpp/ut/api/ErrorCodeTest.cpp
new file mode 100644
index 0000000..42b525f
--- /dev/null
+++ b/src/test/cpp/ut/api/ErrorCodeTest.cpp
@@ -0,0 +1,14 @@
+#include "rocketmq/ErrorCode.h"
+#include "gtest/gtest.h"
+#include <system_error>
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+TEST(ErrorCodeTest, testErrorCode) {
+  std::error_code ec = ErrorCode::BadRequest;
+  if (ec) {
+    std::cout << ec.message() << std::endl;
+  }
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/test/cpp/ut/client/ClientManagerTest.cpp b/src/test/cpp/ut/client/ClientManagerTest.cpp
index 8cfa79e..e14ed02 100644
--- a/src/test/cpp/ut/client/ClientManagerTest.cpp
+++ b/src/test/cpp/ut/client/ClientManagerTest.cpp
@@ -4,6 +4,7 @@
 #include "apache/rocketmq/v1/definition.pb.h"
 #include "gtest/gtest.h"
 #include <memory>
+#include <system_error>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -20,7 +21,9 @@
     metadata_.insert({"name", "Donald.J.Trump"});
   }
 
-  void TearDown() override { client_manager_->shutdown(); }
+  void TearDown() override {
+    client_manager_->shutdown();
+  }
 
 protected:
   std::string resource_namespace_{"mq://test"};
@@ -65,7 +68,7 @@
   QueryRouteRequest request;
   request.mutable_topic()->set_resource_namespace(resource_namespace_);
   request.mutable_topic()->set_name(topic_);
-  auto callback = [&](bool, const TopicRouteDataPtr&) {
+  auto callback = [&](const std::error_code& ec, const TopicRouteDataPtr&) {
     absl::MutexLock lk(&mtx);
     completed = true;
     cv.SignalAll();
@@ -97,7 +100,7 @@
       .WillRepeatedly(testing::Invoke(mock_query_assignment));
   QueryAssignmentRequest request;
   bool callback_invoked = false;
-  auto callback = [&](bool ok, const QueryAssignmentResponse& response) { callback_invoked = true; };
+  auto callback = [&](const std::error_code& ec, const QueryAssignmentResponse& response) { callback_invoked = true; };
 
   client_manager_->queryAssignment(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_), callback);
 
@@ -164,7 +167,7 @@
       .WillRepeatedly(testing::Invoke(mock_async_receive));
   ReceiveMessageRequest request;
 
-  EXPECT_CALL(*receive_message_callback_, onException).Times(testing::AtLeast(1));
+  EXPECT_CALL(*receive_message_callback_, onFailure).Times(testing::AtLeast(1));
 
   client_manager_->receiveMessage(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_),
                                   receive_message_callback_);
@@ -193,7 +196,7 @@
   EXPECT_CALL(*rpc_client_, asyncAck).Times(testing::AtLeast(1)).WillRepeatedly(testing::Invoke(mock_ack));
   AckMessageRequest request;
   bool callback_invoked = false;
-  auto callback = [&](bool ok) { callback_invoked = true; };
+  auto callback = [&](const std::error_code& ec) { callback_invoked = true; };
 
   client_manager_->ack(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_), callback);
 
@@ -222,7 +225,7 @@
   EXPECT_CALL(*rpc_client_, asyncNack).Times(testing::AtLeast(1)).WillRepeatedly(testing::Invoke(mock_nack));
   NackMessageRequest request;
   bool callback_invoked = false;
-  auto callback = [&](bool ok) { callback_invoked = true; };
+  auto callback = [&](const std::error_code& ec) { callback_invoked = true; };
 
   client_manager_->nack(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_), callback);
 
@@ -268,7 +271,8 @@
   EXPECT_TRUE(callback_invoked);
 }
 
-TEST_F(ClientManagerTest, testMultiplexingCall) {}
+TEST_F(ClientManagerTest, testMultiplexingCall) {
+}
 
 TEST_F(ClientManagerTest, testEndTransaction) {
   bool completed = false;
@@ -288,7 +292,7 @@
       .WillRepeatedly(testing::Invoke(mock_end_transaction));
   EndTransactionRequest request;
   bool callback_invoked = false;
-  auto callback = [&](bool ok, const EndTransactionResponse& response) { callback_invoked = true; };
+  auto callback = [&](const std::error_code& ec, const EndTransactionResponse& response) { callback_invoked = true; };
 
   client_manager_->endTransaction(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_), callback);
   {
@@ -342,8 +346,9 @@
       .WillRepeatedly(testing::Invoke(mock_health_check));
   HealthCheckRequest request;
   bool callback_invoked = false;
-  auto callback = [&](const std::string& target_host,
-                      const InvocationContext<HealthCheckResponse>* invocation_context) { callback_invoked = true; };
+  auto callback = [&](const std::error_code& ec, const InvocationContext<HealthCheckResponse>* invocation_context) {
+    callback_invoked = true;
+  };
 
   client_manager_->healthCheck(target_host_, metadata_, request, absl::ToChronoMilliseconds(io_timeout_), callback);
   {
diff --git a/src/test/cpp/ut/rocketmq/AwaitPullCallbackTest.cpp b/src/test/cpp/ut/rocketmq/AwaitPullCallbackTest.cpp
index 3739c33..b286271 100644
--- a/src/test/cpp/ut/rocketmq/AwaitPullCallbackTest.cpp
+++ b/src/test/cpp/ut/rocketmq/AwaitPullCallbackTest.cpp
@@ -1,10 +1,13 @@
+#include <system_error>
+#include <thread>
+
 #include "AwaitPullCallback.h"
 #include "MessageAccessor.h"
+#include "rocketmq/ErrorCode.h"
 #include "rocketmq/MQClientException.h"
 #include "rocketmq/MQMessageExt.h"
 #include "rocketmq/PullResult.h"
 #include "gtest/gtest.h"
-#include <thread>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -51,8 +54,8 @@
   AwaitPullCallback callback(pull_result);
   std::string topic{"Test"};
   auto task = [&]() {
-    MQClientException e("test exception", -1, __FILE__, __LINE__);
-    callback.onException(e);
+    std::error_code ec = ErrorCode::NotImplemented;
+    callback.onFailure(ec);
   };
 
   std::thread t(task);
diff --git a/src/test/cpp/ut/rocketmq/DefaultMQProducerTest.cpp b/src/test/cpp/ut/rocketmq/DefaultMQProducerTest.cpp
index 3d8b3d3..e7b8799 100644
--- a/src/test/cpp/ut/rocketmq/DefaultMQProducerTest.cpp
+++ b/src/test/cpp/ut/rocketmq/DefaultMQProducerTest.cpp
@@ -1,12 +1,14 @@
 #include "rocketmq/DefaultMQProducer.h"
 
+#include <memory>
+#include <mutex>
+#include <system_error>
+#include <utility>
+
 #include "MQClientTest.h"
 #include "ProducerImpl.h"
 #include "rocketmq/CredentialsProvider.h"
 #include "rocketmq/MQSelector.h"
-#include <memory>
-#include <mutex>
-#include <utility>
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -111,13 +113,15 @@
 public:
   UnitTestSendCallback(absl::Mutex& mtx, absl::CondVar& cv, std::string& msg_id, bool& completed)
       : mtx_(mtx), cv_(cv), msg_id_(msg_id), completed_(completed) {}
-  void onSuccess(SendResult& send_result) override {
+
+  void onSuccess(SendResult& send_result) noexcept override {
     absl::MutexLock lk(&mtx_);
     msg_id_ = send_result.getMsgId();
     completed_ = true;
     cv_.SignalAll();
   }
-  void onException(const MQException& e) override {
+
+  void onFailure(const std::error_code& e) noexcept override {
     absl::MutexLock lk(&mtx_);
     completed_ = true;
     cv_.SignalAll();
@@ -136,7 +140,7 @@
   producer->withNameServerResolver(name_server_resolver_);
   producer->setCredentialsProvider(credentials_provider_);
   producer->start();
-  
+
   MQMessage message;
   message.setTopic(topic_);
   message.setBody(body_);
@@ -166,7 +170,9 @@
   message.setTopic(topic_);
   message.setBody(body_);
 
-  SendResult send_result = producer->send(message);
+  std::error_code ec;
+  SendResult send_result = producer->send(message, ec);
+  EXPECT_FALSE(ec);
   ASSERT_EQ(send_result.getMsgId(), message_id_);
   producer->shutdown();
 }
diff --git a/src/test/cpp/ut/rocketmq/ProducerImplTest.cpp b/src/test/cpp/ut/rocketmq/ProducerImplTest.cpp
index a9e253b..b8375b3 100644
--- a/src/test/cpp/ut/rocketmq/ProducerImplTest.cpp
+++ b/src/test/cpp/ut/rocketmq/ProducerImplTest.cpp
@@ -1,4 +1,5 @@
 #include <memory>
+#include <system_error>
 
 #include "ClientManagerFactory.h"
 #include "ClientManagerMock.h"
@@ -80,11 +81,13 @@
   SchedulerImpl scheduler;
   scheduler.start();
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -103,7 +106,9 @@
   producer_->start();
 
   MQMessage message(topic_, tag_, message_body_);
-  producer_->send(message);
+  std::error_code ec;
+  producer_->send(message, ec);
+  EXPECT_FALSE(ec);
   EXPECT_TRUE(cb_invoked);
   producer_->shutdown();
   scheduler.shutdown();
@@ -113,11 +118,13 @@
   SchedulerImpl scheduler;
   scheduler.start();
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -136,28 +143,26 @@
   producer_->start();
 
   MQMessage message(topic_, tag_, message_body_);
-  producer_->send(message, message_group_);
+  message.bindMessageGroup(message_group_);
+  std::error_code ec;
+  producer_->send(message, ec);
+  EXPECT_FALSE(ec);
   EXPECT_TRUE(cb_invoked);
   producer_->shutdown();
   scheduler.shutdown();
 }
 
-class TestMessageQueueSelector : public MessageQueueSelector {
-public:
-  MQMessageQueue select(const std::vector<MQMessageQueue>& mqs, const MQMessage& msg, void* arg) override {
-    return *mqs.begin();
-  }
-};
-
 TEST_F(ProducerImplTest, testSend_WithMessageQueueSelector) {
   SchedulerImpl scheduler;
   scheduler.start();
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -175,10 +180,17 @@
   EXPECT_CALL(*client_manager_, send).Times(testing::AtLeast(1)).WillRepeatedly(testing::Invoke(mock_send));
   producer_->start();
 
-  auto selector = absl::make_unique<TestMessageQueueSelector>();
-
   MQMessage message(topic_, tag_, message_body_);
-  producer_->send(message, selector.get(), nullptr);
+
+  std::error_code ec;
+  auto&& list = producer_->listMessageQueue(topic_, ec);
+
+  EXPECT_FALSE(list.empty());
+
+  message.bindMessageQueue(list[0]);
+
+  producer_->send(message, ec);
+
   EXPECT_TRUE(cb_invoked);
   producer_->shutdown();
   scheduler.shutdown();
@@ -187,12 +199,13 @@
 class TestSendCallback : public SendCallback {
 public:
   TestSendCallback(bool& completed, absl::Mutex& mtx, absl::CondVar& cv) : completed_(completed), mtx_(mtx), cv_(cv) {}
-  void onSuccess(SendResult& send_result) override {
+  void onSuccess(SendResult& send_result) noexcept override {
     absl::MutexLock lk(&mtx_);
     completed_ = true;
     cv_.SignalAll();
   }
-  void onException(const MQException& e) override {
+
+  void onFailure(const std::error_code& ec) noexcept override {
     absl::MutexLock lk(&mtx_);
     completed_ = true;
     cv_.SignalAll();
@@ -208,11 +221,13 @@
   SchedulerImpl scheduler;
   scheduler.start();
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
diff --git a/src/test/cpp/ut/rocketmq/PullConsumerImplTest.cpp b/src/test/cpp/ut/rocketmq/PullConsumerImplTest.cpp
index 90f32d8..26d3de8 100644
--- a/src/test/cpp/ut/rocketmq/PullConsumerImplTest.cpp
+++ b/src/test/cpp/ut/rocketmq/PullConsumerImplTest.cpp
@@ -1,6 +1,7 @@
 #include <chrono>
 #include <memory>
 #include <string>
+#include <system_error>
 
 #include "ClientManagerFactory.h"
 #include "ClientManagerMock.h"
@@ -78,11 +79,13 @@
 
 TEST_F(PullConsumerImplTest, testQueuesFor) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -100,12 +103,12 @@
 class TestPullCallback : public PullCallback {
 public:
   TestPullCallback(bool& success, bool& failure) : success_(success), failure_(failure) {}
-  void onSuccess(const PullResult& pull_result) override {
+  void onSuccess(const PullResult& pull_result) noexcept override {
     success_ = true;
     failure_ = false;
   }
 
-  void onException(const MQException& e) override {
+  void onFailure(const std::error_code& e) noexcept override {
     failure_ = true;
     success_ = false;
   }
@@ -117,11 +120,13 @@
 
 TEST_F(PullConsumerImplTest, testPull) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -179,11 +184,13 @@
 
 TEST_F(PullConsumerImplTest, testPull_gRPC_error) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -224,11 +231,13 @@
 
 TEST_F(PullConsumerImplTest, testPull_biz_error) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -269,11 +278,13 @@
 
 TEST_F(PullConsumerImplTest, testQueryOffset) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -289,7 +300,10 @@
 
   auto mock_query_offset = [&](const std::string& target_host, const Metadata& metadata,
                                const QueryOffsetRequest& request, std::chrono::milliseconds timeout,
-                               const std::function<void(bool, const QueryOffsetResponse&)>& cb) { cb(true, response); };
+                               const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) {
+    std::error_code ec;
+    cb(ec, response);
+  };
 
   EXPECT_CALL(*client_manager_, queryOffset)
       .Times(testing::AtLeast(1))
@@ -307,11 +321,13 @@
 
 TEST_F(PullConsumerImplTest, testQueryOffset_End) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -327,7 +343,10 @@
 
   auto mock_query_offset = [&](const std::string& target_host, const Metadata& metadata,
                                const QueryOffsetRequest& request, std::chrono::milliseconds timeout,
-                               const std::function<void(bool, const QueryOffsetResponse&)>& cb) { cb(true, response); };
+                               const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) {
+    std::error_code ec;
+    cb(ec, response);
+  };
 
   EXPECT_CALL(*client_manager_, queryOffset)
       .Times(testing::AtLeast(1))
@@ -345,11 +364,13 @@
 
 TEST_F(PullConsumerImplTest, testQueryOffset_Timepoint) {
   pull_consumer_->start();
-  auto mock_resolve_route = [this](const std::string& target_host, const Metadata& metadata,
-                                   const QueryRouteRequest& request, std::chrono::milliseconds timeout,
-                                   const std::function<void(bool, const TopicRouteDataPtr& ptr)>& cb) {
-    cb(true, topic_route_data_);
-  };
+  auto mock_resolve_route =
+      [this](const std::string& target_host, const Metadata& metadata, const QueryRouteRequest& request,
+             std::chrono::milliseconds timeout,
+             const std::function<void(const std::error_code& ec, const TopicRouteDataPtr& ptr)>& cb) {
+        std::error_code ec;
+        cb(ec, topic_route_data_);
+      };
 
   EXPECT_CALL(*client_manager_, resolveRoute)
       .Times(testing::AtLeast(1))
@@ -365,7 +386,10 @@
 
   auto mock_query_offset = [&](const std::string& target_host, const Metadata& metadata,
                                const QueryOffsetRequest& request, std::chrono::milliseconds timeout,
-                               const std::function<void(bool, const QueryOffsetResponse&)>& cb) { cb(true, response); };
+                               const std::function<void(const std::error_code&, const QueryOffsetResponse&)>& cb) {
+    std::error_code ec;
+    cb(ec, response);
+  };
 
   EXPECT_CALL(*client_manager_, queryOffset)
       .Times(testing::AtLeast(1))
diff --git a/src/test/cpp/ut/rocketmq/PushConsumerImplTest.cpp b/src/test/cpp/ut/rocketmq/PushConsumerImplTest.cpp
index 0789af4..4bacd98 100644
--- a/src/test/cpp/ut/rocketmq/PushConsumerImplTest.cpp
+++ b/src/test/cpp/ut/rocketmq/PushConsumerImplTest.cpp
@@ -1,6 +1,7 @@
 #include <memory>
 
 #include "gtest/gtest.h"
+#include <system_error>
 
 #include "ClientManagerFactory.h"
 #include "ClientManagerMock.h"
@@ -11,7 +12,6 @@
 #include "grpc/grpc.h"
 #include "rocketmq/MQMessageExt.h"
 #include "rocketmq/MessageListener.h"
-#include "rocketmq/RocketMQ.h"
 
 ROCKETMQ_NAMESPACE_BEGIN
 
@@ -24,7 +24,8 @@
 
 class PushConsumerImplTest : public testing::Test {
 public:
-  PushConsumerImplTest() : message_listener_(absl::make_unique<TestStandardMessageListener>()) {}
+  PushConsumerImplTest() : message_listener_(absl::make_unique<TestStandardMessageListener>()) {
+  }
 
   void SetUp() override {
     grpc_init();
@@ -39,7 +40,9 @@
     push_consumer_->registerMessageListener(message_listener_.get());
   }
 
-  void TearDown() override { grpc_shutdown(); }
+  void TearDown() override {
+    grpc_shutdown();
+  }
 
 protected:
   std::string name_server_list_{"10.0.0.1:9876"};
@@ -63,7 +66,10 @@
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
 
   auto ack_cb = [](const std::string& target_host, const Metadata& metadata, const AckMessageRequest& request,
-                   std::chrono::milliseconds timeout, const std::function<void(bool)>& cb) { cb(true); };
+                   std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& cb) {
+    std::error_code ec;
+    cb(ec);
+  };
 
   EXPECT_CALL(*client_manager_, ack).Times(testing::AtLeast(1)).WillRepeatedly(testing::Invoke(ack_cb));
 
@@ -72,7 +78,7 @@
   bool completed = false;
   absl::Mutex mtx;
   absl::CondVar cv;
-  auto callback = [&](bool ok) {
+  auto callback = [&](const std::error_code& ec) {
     absl::MutexLock lk(&mtx);
     completed = true;
     cv.SignalAll();
@@ -105,7 +111,10 @@
   ON_CALL(*client_manager_, getScheduler).WillByDefault(testing::ReturnRef(scheduler));
 
   auto nack_cb = [](const std::string& target_host, const Metadata& metadata, const NackMessageRequest& request,
-                    std::chrono::milliseconds timeout, const std::function<void(bool)>& cb) { cb(true); };
+                    std::chrono::milliseconds timeout, const std::function<void(const std::error_code&)>& cb) {
+    std::error_code ec;
+    cb(ec);
+  };
 
   EXPECT_CALL(*client_manager_, nack).Times(testing::AtLeast(1)).WillRepeatedly(testing::Invoke(nack_cb));
 
@@ -114,7 +123,7 @@
   bool completed = false;
   absl::Mutex mtx;
   absl::CondVar cv;
-  auto callback = [&](bool ok) {
+  auto callback = [&](const std::error_code& ec) {
     absl::MutexLock lk(&mtx);
     completed = true;
     cv.SignalAll();