RPC 调用概览
一次完整的 URPC 函数调用涉及 Client 端、Server 端、Worker 端的协同。下图展示了从 urpc_func_call() 到 urpc_func_return() 的完整代码路径。
sequenceDiagram
participant C as Client App
participant CP as dp.c (Client)
participant Q as Queue (Jetty)
participant U as URMA (UB Bus)
participant QS as Queue (Jetty)
participant SP as dp.c (Server)
participant S as Server App
C->>CP: urpc_func_call()
Note over CP: 1. channel_get()
2. RR select queues
3. req_entry_get()
4. fill REQ header CP->>Q: queue->ops->send() Q->>U: urma_post_jetty_send_wr() U->>QS: hardware transport QS->>SP: urma_poll_jfc() Note over SP: 5. process_rx_cr()
6. urpc_func_exec()
7. sync_handler() SP->>S: user function executes S-->>SP: return result SP->>QS: urpc_func_return() QS->>U: urma_post_jetty_send_wr() U->>Q: hardware transport Q->>CP: urma_poll_jfc() Note over CP: 8. process_tx_msg()
9. user callback CP-->>C: callback(rsps, err)
2. RR select queues
3. req_entry_get()
4. fill REQ header CP->>Q: queue->ops->send() Q->>U: urma_post_jetty_send_wr() U->>QS: hardware transport QS->>SP: urma_poll_jfc() Note over SP: 5. process_rx_cr()
6. urpc_func_exec()
7. sync_handler() SP->>S: user function executes S-->>SP: return result SP->>QS: urpc_func_return() QS->>U: urma_post_jetty_send_wr() U->>Q: hardware transport Q->>CP: urma_poll_jfc() Note over CP: 8. process_tx_msg()
9. user callback CP-->>C: callback(rsps, err)
Client 端:urpc_func_call()
flowchart TB
N0[uint32_t chid, urpc_call_wr_t *wr, urpc_call_option_t *option]
N1[chid]
N2[channel, option]
N3[l_queue, QUEUE_CTX_TYPE_TX]
N4[channel, ctx]
N5[size-1]
N6[req_entry->valid]
N7[ req_id++; linear_probe ]
N8[urpc_req_head_t *]
N9[0]
N10[req_head, ack, channel_id]
N11[req_head, func_id, len, req_id, func_defined]
N12[func_defined, channel, r_queue, req_entry, wr]
N13[ctx, chid, l_qh, req_id, wr, option]
N14[option, req_entry, func_defined, wr]
N15[l_queue, &queue_wr]
Server 端:接收 REQ 与分发
flowchart TB
N0[jfr_jfc, cr_cnt, cr]
N1[local_queue, msg, cr]
N2[func_id, args, args_sge_num, &rsps, &rsps_sge_num]
N3[func_id]
N4[args, args_sge_num, ctx, &rsps, &rsps_sge_num]
Server 端:返回 RSP
// dp.c: urpc_func_return()
int urpc_func_return(uint64_t urpc_qh, void *req_ctx, urpc_return_wr_t *wr, ...) {
// 1. 获取 queue 和 req_ctx
l_queue = (queue_t *)urpc_qh;
ctx = (req_ctx_t *)req_ctx;
// 2. 分配 tx_ctx
tx_ctx = queue_ctx_get(l_queue, QUEUE_CTX_TYPE_TX);
// 3. 填充 RSP 协议头
rsp_head = (urpc_rsp_head_t *)rsps[0].addr;
urpc_rsp_fill_basic_info(rsp_head, status, client_channel, ack);
urpc_rsp_fill_one_req_info(rsp_head, req_id, response_total_size, option);
// 4. 发送 RSP
l_queue->ops->send(l_queue, &queue_wr);
}
Client 端:接收 ACK/RSP
flowchart TB
N0[jfs_jfc, ...]
N1[jfr_jfc, ...]
N2[req_entry, err_code]
N3[rsps, rsps_sge_num, err, arg, ctx]
tx_ctx 生命周期(双次释放机制)
URPC 使用 tx_ctx_try_put() 实现双次释放:tx_ctx 必须经历两个"完成事件"才能被释放回 slab:
flowchart TB
N0[tx_ctx_t *ctx]
N1[ctx->free_events_completed == FALSE]
N2[QUEUE_CTX_TYPE_TX, ctx]
这解决了 TX CQE 和 ACK/RSP 并发到达时的竞态条件——无论哪个先到,都必须等两个都完成。
调用模式对比
| 模式 | ACK | RSP | tx_ctx 释放时机 | 适用场景 |
|---|---|---|---|---|
EARLY_RSP | 无 | 无 | TX CQE 后立即释放 | 流式调用,无需等待结果 |
WAIT_RSP | 无 | 有 | 收到 RSP 后释放 | 标准同步调用 |
ACK | 有 | 有 | 收到 ACK+RSP 后释放 | 需要确认参数接收 |
ACK | EARLY_RSP | 有 | 无 | 收到 ACK 后释放 | 流式调用,需确认接收 |