chenBright commented on code in PR #3144:
URL: https://github.com/apache/brpc/pull/3144#discussion_r4505565366


##########
src/brpc/policy/baidu_rpc_protocol.cpp:
##########
@@ -823,7 +829,21 @@ void ProcessRpcRequest(InputMessageBase* msg_base) {
 
             butil::IOBuf req_buf;
             int body_without_attachment_size = req_size - 
meta.attachment_size();
-            msg->payload.cutn(&req_buf, body_without_attachment_size);
+#if BRPC_WITH_GDR
+            bool is_gpu_memory = msg->payload.is_gpu_memory();
+            if (is_gpu_memory) {
+                if (!FillReqBufGpu(&req_buf, msg.get(), 
body_without_attachment_size)) {

Review Comment:
   Only the message body is copied back to host memory here; attachments remain 
in GPU memory. When a request has a non-empty attachment and both CRC32C and 
`checksum_with_attachment` are enabled, `DeserializeRpcMessage()` below passes 
the GPU memory address directly to CPU CRC32C through 
`IOBufAsZeroCopyInputStream`, causing an invalid memory access. The four-byte 
checksum check does not prevent this path, and the same issue applies on the 
response side.



##########
src/brpc/rdma_transport.cpp:
##########
@@ -30,11 +30,24 @@ DECLARE_bool(usercode_in_pthread);
 
 extern SocketVarsCollector *g_vars;
 
-void RdmaTransport::Init(Socket *socket, const SocketOptions &options) {
+void RdmaTransport::DoInit(Socket *socket, const SocketOptions &options, bool 
use_gdr) {
     CHECK(_rdma_ep == nullptr);
-    if (options.socket_mode == SOCKET_MODE_RDMA) {
-        _rdma_ep = new rdma::RdmaEndpoint(socket);
-        _rdma_state = RDMA_UNKNOWN;
+    // gdr mode is a special mode of rdma mode.
+    // both rdma mode and gdr mode need init rdma::RdmaEndpoint.
+    if (options.socket_mode == SOCKET_MODE_RDMA ||
+            options.socket_mode == SOCKET_MODE_GDR) {
+        _rdma_ep = new(std::nothrow)rdma::RdmaEndpoint(socket, use_gdr);
+        if (!_rdma_ep) {
+            const int saved_errno = errno;
+            PLOG(ERROR) << "Fail to create RdmaEndpoint";
+            socket->SetFailed(
+                saved_errno, "Fail to create RdmaEndpoint: %s", 
berror(saved_errno));
+            // No endpoint: later code paths must not assume RDMA is active
+            // and dereference _rdma_ep. Mark the transport as not using RDMA.
+            _rdma_state = RDMA_OFF;

Review Comment:
   When endpoint allocation fails, `Socket::OnCreated()` has not yet called 
`HoldHCRelatedRef()`. For a new Socket with only its creation reference, 
`SetFailed()` here synchronously releases the last reference and returns the 
object to the resource pool, but `DoInit()` and `OnCreated()` then continue 
initializing it. Setting `RDMA_OFF` only prevents access to a null endpoint; it 
does not resolve the lifetime issue.



##########
src/butil/config.h.in:
##########
@@ -26,6 +26,12 @@
 #endif
 #define BRPC_WITH_FLATBUFFERS @WITH_FLATBUFFERS_VAL@
 
+// Defined to 1 when brpc is built with GPU Direct RDMA support.
+#ifdef BRPC_WITH_GDR
+#undef BRPC_WITH_GDR
+#endif
+#cmakedefine BRPC_WITH_GDR @WITH_GDR_VAL@

Review Comment:
   `#cmakedefine BRPC_WITH_GDR` checks the CMake variable of the same name, but 
only `WITH_GDR_VAL` and `-DBRPC_WITH_GDR=1` are currently set. As a result, 
when `WITH_GDR=ON` is enabled, this generated header first `#undef`s the macro 
and leaves it undefined, so the GPU implementation being compiled cannot see 
the GPU interfaces in subsequent headers. Please generate a numeric macro 
directly from `WITH_GDR_VAL`, or also set the variable used by `#cmakedefine`.



##########
src/brpc/channel.cpp:
##########
@@ -145,6 +145,8 @@ static ChannelSignature ComputeChannelSignature(const 
ChannelOptions& opt) {
             buf.append("|rdma");
         } else if (opt.socket_mode == SOCKET_MODE_URMA) {
             buf.append("|urma");
+        } else if (opt.socket_mode == SOCKET_MODE_GDR) {
+            buf.append("|gdr");

Review Comment:
   With the default configuration, `ComputeChannelSignature()` returns a zero 
signature at the start and never reaches `|gdr` here. If a GDR Channel is 
created for an address first, followed by a regular `baidu_std` Channel for the 
same address, `SocketMap::Insert()` reuses the former's Socket. The regular 
channel will therefore also receive attachments in GPU memory, and the existing 
`response_attachment().copy_to()` will read the device address directly.



##########
src/brpc/rdma/rdma_endpoint.cpp:
##########
@@ -1133,26 +1148,94 @@ int RdmaEndpoint::DoPostRecv(void* block, size_t 
block_size) {
     return 0;
 }
 
+int RdmaEndpoint::DoPostRecvGDR(void* block, size_t block_size, uint32_t lkey) 
{
+    ibv_recv_wr wr;
+    memset(&wr, 0, sizeof(wr));
+    ibv_sge sge;
+    sge.addr = (uint64_t)block;
+    sge.length = block_size;
+    sge.lkey = lkey;
+    wr.num_sge = 1;
+    wr.sg_list = &sge;
+    //LOG(INFO) << "POST recv: addr=0x" << std::hex << sge.addr
+    //      << std::dec  << " length=0x" << sge.length
+    //      << " lkey=0x" << sge.lkey;
+    //LOG(INFO) << block << " " << _device_allocator->get_lkey();
+    ibv_recv_wr* bad = NULL;
+    int err = ibv_post_recv(_resource->qp, &wr, &bad);
+    if (err != 0) {
+        LOG(WARNING) << "Fail to ibv_post_recv: " << berror(err);
+        return -1;
+    }
+    return 0;
+}
+
 int RdmaEndpoint::PostRecv(uint32_t num, bool zerocopy) {
     // We do the post repeatedly from the _rbuf[_rq_received].
     while (num > 0) {
+#if BRPC_WITH_GDR
+        uint32_t lkey = 0;
+#endif  // if BRPC_WITH_GDR
         if (zerocopy) {
             _rbuf[_rq_received].clear();
-            butil::IOBufAsZeroCopyOutputStream os(&_rbuf[_rq_received],
-                    g_rdma_recv_block_size + IOBUF_BLOCK_HEADER_LEN);
-            int size = 0;
-            if (!os.Next(&_rbuf_data[_rq_received], &size)) {
-                // Memory is not enough for preparing a block
-                PLOG(WARNING) << "Fail to allocate rbuf";
-                return -1;
-            } else {
-                CHECK_EQ(static_cast<uint32_t>(size), g_rdma_recv_block_size);
+
+#if BRPC_WITH_GDR
+            if (_use_gdr) {
+                butil::gdr::BlockPoolAllocator* device_allocator = 
butil::gdr::BlockPoolAllocators::GetInstance()->get_gpu_allocator();
+                void* device_ptr = 
device_allocator->AllocateRaw(g_gdr_recv_block_size);
+                if (device_ptr == nullptr) {
+                    // GPU pool exhausted; abort this post, the caller will 
retry.
+                    PLOG(WARNING) << "Fail to allocate gpu rbuf";
+                    return -1;
+                }
+                auto deleter = [device_allocator](void* data) { 
device_allocator->DeallocateRaw(data); };
+                lkey = device_allocator->get_lkey(device_ptr);
+                // Tag the block as GPU memory so that IOBuf::is_gpu_memory()
+                // works deterministically. The lkey is carried via `data_meta'
+                // so the recv path can locate the correct memory region key.
+                if (_rbuf[_rq_received].append_user_data_gpu(
+                        device_ptr, g_gdr_recv_block_size, deleter, lkey) != 
0) {
+                    // Failed to attach the GPU block to the IOBuf (e.g. the
+                    // IOBuf metadata block allocation failed). Release the
+                    // device block back to the pool and abort this post; the
+                    // caller will retry. Publishing _rbuf_data without a
+                    // successful append would leave the posted block ownerless
+                    // and silently drop the receive on completion.
+                    PLOG(WARNING) << "Fail to append gpu rbuf to IOBuf";
+                    device_allocator->DeallocateRaw(device_ptr);
+                    return -1;
+                }
+                _rbuf_data[_rq_received] = device_ptr;
+            } else
+#endif  // if BRPC_WITH_GDR
+            {
+                butil::IOBufAsZeroCopyOutputStream os(&_rbuf[_rq_received],
+                        g_rdma_recv_block_size + IOBUF_BLOCK_HEADER_LEN);
+                int size = 0;
+                if (!os.Next(&_rbuf_data[_rq_received], &size)) {
+                    // Memory is not enough for preparing a block
+                    PLOG(WARNING) << "Fail to allocate rbuf";
+                    return -1;
+                } else {
+                    CHECK_EQ(static_cast<uint32_t>(size), 
g_rdma_recv_block_size);
+                }
             }
         }
-        if (DoPostRecv(_rbuf_data[_rq_received], g_rdma_recv_block_size) < 0) {
-            _rbuf[_rq_received].clear();
-            return -1;
+#if BRPC_WITH_GDR
+        if (_use_gdr) {
+            if (DoPostRecvGDR(_rbuf_data[_rq_received], g_gdr_recv_block_size, 
lkey) < 0) {

Review Comment:
   When `rdma_recv_zerocopy=false`, a zero-byte ACK does not enter the GDR 
`zerocopy=true` branch above and instead leads to `PostRecv(1, false)`. This 
reuses the GPU memory address, but `lkey` remains at its initial value of `0`; 
the next data receive may therefore cause a protection error and disconnect the 
connection.



##########
src/butil/iobuf.cpp:
##########
@@ -768,6 +771,46 @@ size_t IOBuf::cutn(IOBuf* out, size_t n) {
     return saved_n;
 }
 
+#if BRPC_WITH_GDR
+size_t IOBuf::cutn_from_gpu(IOBuf* out, size_t n) {
+    if (n == 0) {
+      return 0;
+    }
+
+    butil::gdr::BlockPoolAllocator* host_allocator = 
butil::gdr::BlockPoolAllocators::GetInstance()->get_cpu_allocator();
+    bool alloc_from_host_alloc = (n <= host_allocator->get_block_size());
+    void* mem = NULL;
+    if (alloc_from_host_alloc) {
+      mem = host_allocator->AllocateRaw(n);
+    } else {
+      mem = malloc(n);
+    }
+
+    if (mem == NULL) {
+        return 0;
+    }
+    size_t saved_n = copy_from_gpu(mem, n, 0, false);
+    if (saved_n > 0) {
+      if (alloc_from_host_alloc) {
+        auto deleter = [host_allocator](void* data) { 
host_allocator->DeallocateRaw(data); };
+        out->append_user_data(mem, saved_n, deleter);
+      } else {
+        auto deleter = [](void* data) { free(data); };
+        out->append_user_data(mem, saved_n, deleter);
+      }
+      pop_front(saved_n);

Review Comment:
   `append_user_data()` can return `-1` if block metadata allocation fails, but 
this code still calls `pop_front(saved_n)` and returns a successful length. The 
destination receives no data, while the source data has already been consumed 
and the temporary host memory leaks. The protocol layer's length check also 
incorrectly reports success.



##########
src/brpc/policy/baidu_rpc_protocol_gpu.cpp:
##########
@@ -0,0 +1,294 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#if BRPC_WITH_GDR
+
+#include <google/protobuf/descriptor.h>         // MethodDescriptor
+#include <google/protobuf/message.h>            // Message
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/text_format.h>
+
+#include "butil/gpu/gpu_block_pool.h"
+#include "butil/iobuf.h"                         // butil::IOBuf
+#include "butil/logging.h"                       // LOG()
+#include "butil/memory/scope_guard.h"
+#include "butil/raw_pack.h"                      // RawPacker RawUnpacker
+#include "butil/strings/string_util.h"
+
+#include "json2pb/json_to_pb.h"
+#include "json2pb/pb_to_json.h"
+#include "brpc/controller.h"                    // Controller
+#include "brpc/socket.h"                        // Socket
+#include "brpc/server.h"                        // Server
+#include "brpc/span.h"
+#include "brpc/compress.h"                      // ParseFromCompressedData
+#include "brpc/checksum.h"
+#include "brpc/stream_impl.h"
+#include "brpc/rpc_dump.h"                      // SampledRequest
+#include "brpc/rpc_pb_message_factory.h"
+#include "brpc/policy/baidu_rpc_meta.pb.h"      // RpcRequestMeta
+#include "brpc/policy/baidu_rpc_protocol.h"
+#include "brpc/policy/most_common_message.h"
+#include "brpc/policy/streaming_rpc_protocol.h"
+#include "brpc/details/usercode_backup_pool.h"
+#include "brpc/details/controller_private_accessor.h"
+#include "brpc/details/server_private_accessor.h"
+
+namespace brpc {
+namespace policy {
+
+// Notes:
+// 1. 12-byte header [PRPC][body_size][meta_size]
+// 2. body_size and meta_size are in network byte order
+// 3. Use service->full_name() + method_name to specify the method to call
+// 4. `attachment_size' is set iff request/response has attachment
+// 5. Not supported: chunk_info
+
+// Pack header into `buf'
+
+const int header_size = 12;
+// if we recv data into gpu, the header/meta/body will be copied to cpu and 
processed.
+// in to to limit the count of d2h, we will prefetch some bytes from gpu to 
cpu.
+// if header_size + meta_size + body_size(without attachment) is less than the
+// prefetch size, then one d2h is enough for one rpc.
+// The prefetch size is derived from the CPU block pool's block size so that 
the
+// allocation never exceeds the allocator's single-block limit and stays 
effective
+// if the block size changes.
+
+static const int kDefaultPrefetchD2HSize = 512;
+
+static int GetPrefetchD2HSize(butil::gdr::BlockPoolAllocator* host_allocator) {
+    const int64_t block_size = host_allocator->get_block_size();
+    return block_size > 0 ? static_cast<int>(block_size)
+                          : kDefaultPrefetchD2HSize;
+}
+
+ParseResult ParseRpcMessageGpu(butil::IOBuf* source, Socket* socket,
+                            bool /*read_eof*/, const void*) {
+
+    char header_buf[12];
+    size_t n = 0;
+    uint32_t body_size;
+    uint32_t meta_size;
+    ParseError pe = PARSE_OK;
+
+    void* prefetch_d2h_data = NULL;
+    bool is_gpu_memory = source->is_gpu_memory();
+    if (!is_gpu_memory) {
+        // A non-GPU buffer here means the message arrived through a non-GDR
+        // path; reject it instead of aborting the whole process.
+        LOG(ERROR) << "RpcMessage is not in gpu!!!";
+        return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+    }
+    butil::gdr::BlockPoolAllocator* host_allocator = 
butil::gdr::BlockPoolAllocators::GetInstance()->get_cpu_allocator();
+    const int prefetch_d2h_size = GetPrefetchD2HSize(host_allocator);
+    prefetch_d2h_data = host_allocator->AllocateRaw(prefetch_d2h_size);
+    if (prefetch_d2h_data == NULL) {
+        // Pool exhausted or single-block limit hit: ask the caller to retry
+        // instead of crashing the whole server.
+        LOG(ERROR) << "alloc host data for prefetch d2h failed, size="
+                   << prefetch_d2h_size;
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+    }
+
+    // n is the bytes we real frefetch, n maybe less than prefetch_d2h_size;
+    n = source->copy_from_gpu(prefetch_d2h_data, prefetch_d2h_size);
+    size_t copy_size = n > 12 ? 12 : n;
+    memcpy(header_buf, prefetch_d2h_data, copy_size);
+
+    do {
+        // NOTE: unlike the CPU ParseRpcMessage, malformed GPU frames must NOT
+        // return PARSE_ERROR_TRY_OTHERS: InputMessengerProcessor would then
+        // dispatch the same GPU-resident IOBuf to non-GDR parsers whose
+        // copy_to cannot dereference device memory and would crash. A GDR
+        // connection only speaks baidu_std, so treat this as terminal.
+        if (n >= 4) {
+            void* dummy = header_buf;
+            if (*(const uint32_t*)dummy != *(const uint32_t*)"PRPC") {
+                pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+                break;
+            }
+        } else {
+            if (memcmp(header_buf, "PRPC", n) != 0) {
+                pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+                break;
+            }
+        }
+        if (n < sizeof(header_buf)) {
+            pe = PARSE_ERROR_NOT_ENOUGH_DATA;
+            break;
+        }
+        butil::RawUnpacker(header_buf + 
4).unpack32(body_size).unpack32(meta_size);
+        if (body_size > FLAGS_max_body_size) {
+            // We need this log to report the body_size to give users some 
clues
+            // which is not printed in InputMessenger.
+            LOG(ERROR) << "body_size=" << body_size << " from "
+                       << socket->remote_side() << " is too large";
+            pe = PARSE_ERROR_TOO_BIG_DATA;
+            break;
+        } else if (source->length() < sizeof(header_buf) + body_size) {
+            pe = PARSE_ERROR_NOT_ENOUGH_DATA;
+            break;
+        }
+        if (meta_size > body_size) {
+            LOG(ERROR) << "meta_size=" << meta_size << " is bigger than 
body_size="
+                       << body_size;
+            // Pop the message
+            source->pop_front(sizeof(header_buf) + body_size);
+            pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+            break;
+        }
+    } while (0);
+
+    if (pe != PARSE_OK) {
+        host_allocator->DeallocateRaw(prefetch_d2h_data);
+        return MakeParseError(pe);
+    }
+
+    source->pop_front(sizeof(header_buf));
+    MostCommonMessage* msg = MostCommonMessage::Get();
+
+    if (header_size + meta_size <= n) {
+        auto deleter = [host_allocator, prefetch_d2h_data](void* data) { 
host_allocator->DeallocateRaw(prefetch_d2h_data); };
+        // `n` is the number of bytes actually prefetched from GPU. It is 
stored
+        // in the meta's `data_meta' so that FillReqBufGpu/FillResBufGpu can
+        // decide whether the prefetched host buffer also covers the body (and
+        // thus avoid a second D2H copy). This block is host memory 
(prefetched),
+        // NOT GPU memory, so we deliberately use append_user_data_with_meta
+        // (not append_user_data_gpu) to keep is_gpu_memory() returning false.
+        if (msg->meta.append_user_data_with_meta(
+                (char*)prefetch_d2h_data + header_size, meta_size, deleter, n) 
!= 0) {
+            // append failed (internal malloc): the prefetch buffer is NOT
+            // owned by anyone; release it back to the pool instead of leaking.
+            LOG(ERROR) << "Fail to append prefetched meta to IOBuf";
+            host_allocator->DeallocateRaw(prefetch_d2h_data);
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);

Review Comment:
   Before returning here, the code has already called `pop_front(12)` and 
obtained `msg`. Returning `NOT_ENOUGH_DATA` at this point makes the caller wait 
for new input, but the next parse still starts at the `PRPC` frame header while 
the source buffer now starts at the metadata. Meanwhile, `msg` is not returned 
to the message pool.



##########
src/brpc/policy/baidu_rpc_protocol_gpu.cpp:
##########
@@ -0,0 +1,294 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#if BRPC_WITH_GDR
+
+#include <google/protobuf/descriptor.h>         // MethodDescriptor
+#include <google/protobuf/message.h>            // Message
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/text_format.h>
+
+#include "butil/gpu/gpu_block_pool.h"
+#include "butil/iobuf.h"                         // butil::IOBuf
+#include "butil/logging.h"                       // LOG()
+#include "butil/memory/scope_guard.h"
+#include "butil/raw_pack.h"                      // RawPacker RawUnpacker
+#include "butil/strings/string_util.h"
+
+#include "json2pb/json_to_pb.h"
+#include "json2pb/pb_to_json.h"
+#include "brpc/controller.h"                    // Controller
+#include "brpc/socket.h"                        // Socket
+#include "brpc/server.h"                        // Server
+#include "brpc/span.h"
+#include "brpc/compress.h"                      // ParseFromCompressedData
+#include "brpc/checksum.h"
+#include "brpc/stream_impl.h"
+#include "brpc/rpc_dump.h"                      // SampledRequest
+#include "brpc/rpc_pb_message_factory.h"
+#include "brpc/policy/baidu_rpc_meta.pb.h"      // RpcRequestMeta
+#include "brpc/policy/baidu_rpc_protocol.h"
+#include "brpc/policy/most_common_message.h"
+#include "brpc/policy/streaming_rpc_protocol.h"
+#include "brpc/details/usercode_backup_pool.h"
+#include "brpc/details/controller_private_accessor.h"
+#include "brpc/details/server_private_accessor.h"
+
+namespace brpc {
+namespace policy {
+
+// Notes:
+// 1. 12-byte header [PRPC][body_size][meta_size]
+// 2. body_size and meta_size are in network byte order
+// 3. Use service->full_name() + method_name to specify the method to call
+// 4. `attachment_size' is set iff request/response has attachment
+// 5. Not supported: chunk_info
+
+// Pack header into `buf'
+
+const int header_size = 12;
+// if we recv data into gpu, the header/meta/body will be copied to cpu and 
processed.
+// in to to limit the count of d2h, we will prefetch some bytes from gpu to 
cpu.
+// if header_size + meta_size + body_size(without attachment) is less than the
+// prefetch size, then one d2h is enough for one rpc.
+// The prefetch size is derived from the CPU block pool's block size so that 
the
+// allocation never exceeds the allocator's single-block limit and stays 
effective
+// if the block size changes.
+
+static const int kDefaultPrefetchD2HSize = 512;
+
+static int GetPrefetchD2HSize(butil::gdr::BlockPoolAllocator* host_allocator) {
+    const int64_t block_size = host_allocator->get_block_size();
+    return block_size > 0 ? static_cast<int>(block_size)
+                          : kDefaultPrefetchD2HSize;
+}
+
+ParseResult ParseRpcMessageGpu(butil::IOBuf* source, Socket* socket,
+                            bool /*read_eof*/, const void*) {
+
+    char header_buf[12];
+    size_t n = 0;
+    uint32_t body_size;
+    uint32_t meta_size;
+    ParseError pe = PARSE_OK;
+
+    void* prefetch_d2h_data = NULL;
+    bool is_gpu_memory = source->is_gpu_memory();
+    if (!is_gpu_memory) {
+        // A non-GPU buffer here means the message arrived through a non-GDR
+        // path; reject it instead of aborting the whole process.
+        LOG(ERROR) << "RpcMessage is not in gpu!!!";
+        return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+    }
+    butil::gdr::BlockPoolAllocator* host_allocator = 
butil::gdr::BlockPoolAllocators::GetInstance()->get_cpu_allocator();
+    const int prefetch_d2h_size = GetPrefetchD2HSize(host_allocator);
+    prefetch_d2h_data = host_allocator->AllocateRaw(prefetch_d2h_size);
+    if (prefetch_d2h_data == NULL) {
+        // Pool exhausted or single-block limit hit: ask the caller to retry
+        // instead of crashing the whole server.
+        LOG(ERROR) << "alloc host data for prefetch d2h failed, size="
+                   << prefetch_d2h_size;
+        return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+    }
+
+    // n is the bytes we real frefetch, n maybe less than prefetch_d2h_size;
+    n = source->copy_from_gpu(prefetch_d2h_data, prefetch_d2h_size);
+    size_t copy_size = n > 12 ? 12 : n;
+    memcpy(header_buf, prefetch_d2h_data, copy_size);
+
+    do {
+        // NOTE: unlike the CPU ParseRpcMessage, malformed GPU frames must NOT
+        // return PARSE_ERROR_TRY_OTHERS: InputMessengerProcessor would then
+        // dispatch the same GPU-resident IOBuf to non-GDR parsers whose
+        // copy_to cannot dereference device memory and would crash. A GDR
+        // connection only speaks baidu_std, so treat this as terminal.
+        if (n >= 4) {
+            void* dummy = header_buf;
+            if (*(const uint32_t*)dummy != *(const uint32_t*)"PRPC") {
+                pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+                break;
+            }
+        } else {
+            if (memcmp(header_buf, "PRPC", n) != 0) {
+                pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+                break;
+            }
+        }
+        if (n < sizeof(header_buf)) {
+            pe = PARSE_ERROR_NOT_ENOUGH_DATA;
+            break;
+        }
+        butil::RawUnpacker(header_buf + 
4).unpack32(body_size).unpack32(meta_size);
+        if (body_size > FLAGS_max_body_size) {
+            // We need this log to report the body_size to give users some 
clues
+            // which is not printed in InputMessenger.
+            LOG(ERROR) << "body_size=" << body_size << " from "
+                       << socket->remote_side() << " is too large";
+            pe = PARSE_ERROR_TOO_BIG_DATA;
+            break;
+        } else if (source->length() < sizeof(header_buf) + body_size) {
+            pe = PARSE_ERROR_NOT_ENOUGH_DATA;
+            break;
+        }
+        if (meta_size > body_size) {
+            LOG(ERROR) << "meta_size=" << meta_size << " is bigger than 
body_size="
+                       << body_size;
+            // Pop the message
+            source->pop_front(sizeof(header_buf) + body_size);
+            pe = PARSE_ERROR_ABSOLUTELY_WRONG;
+            break;
+        }
+    } while (0);
+
+    if (pe != PARSE_OK) {
+        host_allocator->DeallocateRaw(prefetch_d2h_data);
+        return MakeParseError(pe);
+    }
+
+    source->pop_front(sizeof(header_buf));
+    MostCommonMessage* msg = MostCommonMessage::Get();
+
+    if (header_size + meta_size <= n) {
+        auto deleter = [host_allocator, prefetch_d2h_data](void* data) { 
host_allocator->DeallocateRaw(prefetch_d2h_data); };
+        // `n` is the number of bytes actually prefetched from GPU. It is 
stored
+        // in the meta's `data_meta' so that FillReqBufGpu/FillResBufGpu can
+        // decide whether the prefetched host buffer also covers the body (and
+        // thus avoid a second D2H copy). This block is host memory 
(prefetched),
+        // NOT GPU memory, so we deliberately use append_user_data_with_meta
+        // (not append_user_data_gpu) to keep is_gpu_memory() returning false.
+        if (msg->meta.append_user_data_with_meta(
+                (char*)prefetch_d2h_data + header_size, meta_size, deleter, n) 
!= 0) {
+            // append failed (internal malloc): the prefetch buffer is NOT
+            // owned by anyone; release it back to the pool instead of leaking.
+            LOG(ERROR) << "Fail to append prefetched meta to IOBuf";
+            host_allocator->DeallocateRaw(prefetch_d2h_data);
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+        source->pop_front(meta_size);
+    } else {
+        host_allocator->DeallocateRaw(prefetch_d2h_data);
+        if (source->cutn_from_gpu(&msg->meta, meta_size) != meta_size) {
+            // Host pool exhausted or the GPU copy failed: meta is NOT cut out
+            // of `source'. Continuing would make the following cutn treat the
+            // meta bytes as payload and silently corrupt the message.
+            LOG(ERROR) << "Fail to cut meta_size=" << meta_size << " from gpu";
+            return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+        }
+    }
+    source->cutn(&msg->payload, body_size - meta_size);
+    return MakeMessage(msg);
+}
+
+
+// Returns true on success. Returns false (nothing cut out of msg->payload)
+// when the GPU -> host copy fails; the caller should fail the request.
+bool FillReqBufGpu(butil::IOBuf* req_buf, MostCommonMessage* msg, int 
body_without_attachment_size) {
+    int meta_size = msg->meta.size();
+    if (!msg->payload.is_gpu_memory()) {
+        // Should not happen on a GDR connection; do not crash the server.
+        LOG(ERROR) << "message is not on gpu!!!";
+        return false;
+    }
+    int64_t real_prefetch_d2h_size = msg->meta.get_first_data_meta();
+    if (header_size + meta_size + body_without_attachment_size <= 
real_prefetch_d2h_size) {
+        void* data = msg->meta.get_first_data_ptr();
+        if (data == nullptr) {
+            LOG(ERROR) << "illegal prefetched data";
+            return false;
+        }
+        req_buf->append((char*)data + meta_size, body_without_attachment_size);
+        msg->payload.pop_front(body_without_attachment_size);
+    } else {
+        if (msg->payload.cutn_from_gpu(req_buf, body_without_attachment_size)
+                != (size_t)body_without_attachment_size) {
+            LOG(ERROR) << "Fail to cut " << body_without_attachment_size
+                       << " bytes from gpu";
+            return false;
+        }
+    }
+    return true;
+}
+
+// Returns true on success; on failure cntl is SetFailed and the caller must
+// stop processing this response (res_buf_ptr is left untouched).
+bool FillResBufGpu(butil::IOBuf* res_buf, MostCommonMessage* msg, const 
RpcMeta& meta,
+                   butil::IOBuf** res_buf_ptr, Controller* cntl) {
+    const int res_size = msg->payload.length();
+    int meta_size = msg->meta.size();
+    if (!msg->payload.is_gpu_memory()) {
+        // Should not happen on a GDR connection; fail this response instead
+        // of crashing the whole server.
+        cntl->SetFailed(ERESPONSE, "response payload is not on gpu memory");
+        return false;
+    }
+    if (meta.has_attachment_size()) {
+        if (meta.attachment_size() > res_size) {
+            cntl->SetFailed(
+                    ERESPONSE, "attachment_size=%d is larger than 
response_size=%d",
+                    meta.attachment_size(), res_size);
+            return false;
+        }
+        int body_without_attachment_size = res_size - meta.attachment_size();

Review Comment:
   This omits the `attachment_size < 0` check present in the CPU response path. 
If the current payload contains only `A` and the attachment length is `-1`, the 
message body length is calculated as `2`. When the prefetch also includes the 
next frame, the fast path copies out `AP`, where `P` comes from the next 
frame's `PRPC`. The `SerializedResponse` path passes it directly to the caller 
as a successful response.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to