10.GRPC C++源碼閱讀 FD管理

轉載自: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章