轉載自:http://www.anger6.com/?p=457
本篇文章講述gRPC如何管理文件描述符,如何處理fd上的事件。
經過前面幾篇文章的學習,我們知道了completion_queue在grpc中的作用。那麼它究竟是如何工作的,這篇文章將詳細講述。
grpc_completion_queue在上面左下角位置,它主要有2部分內容。vtable,poller_vtable.
- vtable
爲內部的實際緩衝隊列服務,包括向隊列中添加完成事件,取出並處理完成事件等。這裏的完成事件有可能是rpc請求。
- poller_vtable
爲內部管理的pollset服務,包括epoll事件監聽,epoll事件處理。
隊列結構的末尾是隊列數據和用於poller的數據。
隊列數據,對於GRPC_CQ_NEXT類型隊列是cq_next_data;對於GRPC_CQ_PLUCK類型的隊列是cq_pluck_data
poller數據,對於GRPC_CQ_DEFAULT_POLLING和GRPC_CQ_NON_LISTENING類型的隊列是grpc_pollset;對於GRPC_CQ_NON_POLLING類型的隊列是non_polling_poller.
- cq_next_data
爲上面的vtable服務,用於實際存儲完成事件。
- cq_pollset
爲上面的poller_vtable服務,用於存儲epoll fd和相關fd.
介紹完相關數據結構,再來看一下cq相關的主要流程。
- 1.創建流程
grpc_completion_queue* grpc_completion_queue_create_internal(
grpc_cq_completion_type completion_type,
grpc_cq_polling_type polling_type)
根據隊列類型和poll類型初始化上文提到的vtable和poller_vtable.
const cq_vtable* vtable = &g_cq_vtable[completion_type];
const cq_poller_vtable* poller_vtable =
&g_poller_vtable_by_poller_type[polling_type];
cq->vtable = vtable;
cq->poller_vtable = poller_vtable;
然後初始化上文提到的cq_next_data和cq_pollset
poller_vtable->init(POLLSET_FROM_CQ(cq), &cq->mu);
vtable->init(DATA_FROM_CQ(cq));
對於cq_next_data,主要是初始化隊列,這是一個無鎖隊列,後面會講解無鎖隊列的實現原理
static void cq_init_next(void* ptr) {
cq_next_data* cqd = static_cast>(ptr); / Initial count is dropped by grpc_completion_queue_shutdown */ gpr_atm_no_barrier_store(&cqd->pending_events, 1);
cqd->shutdown_called = false;
gpr_atm_no_barrier_store(&cqd->things_queued_ever, 0);
cq_event_queue_init(&cqd->queue);
}
對於cq_pollset,初始化grpc_pollset
static void pollset_init(grpc_pollset* pollset, gpr_mu** mu) {
gpr_mu_init(&pollset->mu);
gpr_atm_no_barrier_store(&pollset->worker_count, 0);
pollset->active_pollable = POLLABLE_REF(g_empty_pollable, “pollset”);
pollset->kicked_without_poller = false;
pollset->shutdown_closure = nullptr;
pollset->already_shutdown = false;
pollset->root_worker = nullptr;
pollset->containing_pollset_set_count = 0;
*mu = &pollset->mu;
}
最後安裝隊列shutdown時執行的回收pollset的回調閉包
GRPC_CLOSURE_INIT(&cq->pollset_shutdown_done, on_pollset_shutdown_done, cq,
grpc_schedule_on_exec_ctx);
注意,這個閉包安裝在grpc_schedule_on_exec_ctx調度器上,根據前面文章的講述,會在閉包調度的當前線程執行。
- 2.銷燬流程
看cq的銷燬流程之前,先來看一下grpc_server的退出流程。我們的主程序會阻塞在其wait調用上。
server->Wait();
這個函數會等在條件變量上,喚醒後會檢查是否已經退出。
void Server::Wait() {
std::unique_lock lock(mu_);
while (started_ && !shutdown_notified_) {
shutdown_cv_.wait(lock);
}
}
那麼shutdown_notified_什麼時候爲true呢?
答案是我們主動調用server的shutdown方法時。
shutdown方法的流程:
- grpc_server_shutdown_and_notify
會做以下操作:
- 殺掉所有未處理的rpc請求,不包括通過grpc_server_request_call和grpc_server_request_registered發起的請求。如何殺掉未處理的請求可以進一下查看”kill_pending_work_locked”函數。
- 關閉所有的監聽,不再接收任何新的請求。
- 通過傳輸層向所有的通道發送關閉消息(詳細過程見”channel_broadcaster_shutdown”函數).
傳輸層保證:
- 向客戶端發送shutdown.(比如,HTTP2發送GOAWAY)
- 如果server有正在處理的請求,連接會等到所有調用完成後再關閉。
- 一旦沒有正在處理中的請求,channel就會關閉。
- 關閉所有線程池
關閉線程池時會做2件事
void Shutdown() override {
ThreadManager::Shutdown();
server_cq_->Shutdown();
}
- 關閉所有線程
- 將cq關閉shutdown.
前面我們講過每個cq有一個線程池服務,這裏就是前面說的cq的shutdown的地方。
學習了server的shutdown流程,也知道了cq關閉的時機,我們看下cq關閉都做了些什麼。
void grpc_completion_queue_shutdown(grpc_completion_queue* cq) {
grpc_core::ExecCtx exec_ctx;
cq->vtable->shutdown(cq);
}
聲明瞭exec_ctx,用於調度當前執行路徑上的閉包。然後調用vtable的shutdown方法
主要做了以下操作:
cq_finish_shutdown_next(cq);
cq->poller_vtable->shutdown(POLLSET_FROM_CQ(cq), &cq->pollset_shutdown_done);
調用poller_vtable的shutdown操作
1