ccfの計画

今実装中のccfの高水準なAPIについてメモ。(前に書いたccfの件はwavyのちょっとしたラッパ程度でしかない)


connectionでCuriously Recurring Template Patternを使っているのはかなり本質的で、何段継承してもオーバーヘッドが無いし、どの段階でもフックできるのでカスタマイズもできる*1

まず基本のconnectionクラスにRPC要求・応答を分岐する層をかぶせる:

template <typename IMPL>
class rpc_connection : public connection<IMPL> {
    // ...
    void process_message(msgobj msg, auto_zone& z);

    // process_messageから呼ばれる
    void process_request(method_t method, msgobj param, msgid_t msgid, auto_zone& z);

    // process_requestから呼ばれる
    void dispatch_request(method_t method, msgobj param, responder& response, auto_zone& z);

    // process_messageから呼ばれる
    void process_response(msgobj result, msgobj error, msgid_t msgid, auto_zone& z);
};

(このrpc_connectionだけでもかなり便利で、他のところで使い回せる)


その上に「コネクション」と「セッション」を分離する層をかぶせる。セッションはこちらから送るRPC要求のメッセージIDにコールバック関数を紐づけておいて、応答を受け取ったときに紐づけられたコールバック関数を呼び出す…などなどするクラス。

template <typename IMPL>
class managed_connection : public rpc_connection<IMPL> {
    // 引数にセッションマネージャを取る
    managed_connection(int fd, basic_session_manager* manager,
            shared_session session, address locator);

    // rpc_connection<IMPL>::process_requestをフック
    void process_request(method_t method, msgobj param,
            msgid_t msgid, auto_zone& z);

    // rpc_connection<IMPL>::process_responseをフック
    void process_response(msgobj result, msgobj error,
            msgid_t msgid, auto_zone& z);

    // ...

コンストラクタでsession_managerとsessionを受け取って、process_requestをsession_managerに中継し、process_responseをsessionに中継する。


コネクションとセッションを分離する目的は以下:

  • コネクションをRPCセッションの下に隠蔽する
    • 明示的に「まず接続して、接続し終わったらコールバック関数を呼んでもらって…」とやらなくてもメッセージが送れる
    • ↑メッセージを送ろうとした段階でセッションを作って、後で(非同期に)そのセッションにコネクションを登録する
  • 相手先ホスト=セッション(!=コネクション)になる
    • 複数のコネクションで負荷分散したり(1つのホストが複数のNICを持っている場合)
    • bondingでactive-activeは色々アレなのでアプリケーションレベルで
  • ネットワークが瞬断したとき、再接続してそのまま正常に続行できる
    • 要求->応答のコンテキストも途切れない


sessionは↓こんな感じで:

class sessoin {
    // RPC呼び出し
    template <typename Message>
    void call(Message& parameter,
            shared_zone life, callback_t callback,
            unsigned short timeout = 10);

    // コネクションを追加・削除
    void add_connection(int fd, const address& locator);
    // 削除はmanaged_connection::~managed_connectionから自動で呼ばれる
    void remove_connection(int fd);

    // managed_connection::process_responseから呼ばれる
    void process_response(msgobj result, msgobj error,
            msgid_t msgid, auto_zone& z);
};

callでRPC呼び出しする。templateのMessageはMessagePackでシリアライズ可能な任意のクラス…なのだが、static const uint32_t Message::idがRPCのメソッドIDになっているなどの制約がある。


で、そのMessageクラスを生成するRPCスタブジェネレータ*2がある(作りかけ)。書き方はこんな感じで:

@message Get             # これ自体がMessagePackでシリアライズ可能になる
    std::string key;     # MessagePackでシリアライズ可能な任意のクラス
    uint32_t flags = 0;  # デフォルト引数に対応
@end = 1                 # これが static const uint32_t Get::id になる


一方session_managerは↓こんな感じ:

template <typename Identifier, typename IMPL>
class session_manager : public basic_session_manager {
    // identifierに対応するsessionを取り出すか、無ければ作る
    shared_session get_session(const Identifier& id);

    // managed_connectionから呼ばれる
    virtual void dispatch(shared_session from,
            method_t method, msgobj param,
            session_responder response, auto_zone& z) = 0;

    // get_sessionで新しいsessionが作られたら呼ばれる
    // session_created(const identifer_t& addr, shared_session s);
};

session_managerを継承してdispatchを実装してやればRPCサーバーが完成する。引数に session_responder というのが渡されていて、.result(true); などとやるとRPC応答を返送してくれる。


Identifierにはアプリケーションによって独自の「ノード識別子」を定義できる。1つのノード識別子に対して1つのセッションが対応する。アドレスが違っても*3識別子が==なら、複数のコネクションを1つのセッションにまとめられる。

Identiferから相手のIP+ポートが引けるなら、session_createdの中で非同期にccf::core::connectしたりできる…が、それは派生クラスの実装に任される。ここもCuriously Recurring Template Pattern。


しかしフツーに相手に接続するだけでいいなら、Identifierには単なるIP+ポートを使うだろうし、勝手にconnect(2)もしてほしい。というわけで典型的な用途向けにsession_managerを継承したクラスがいくつかある:

class server : public session_manager<address, server>;
class client : public session_manager<address, client>;
class custer : public session_manager<未定, cluster>;


ここから先は未実装。

*1:コンパイルは遅くなる><

*2:RPCスタブジェネレータと言うか、クラス定義とMessagePackのシリアライズ・デシリアライズ関数を自動で生成する

*3:他にこちらから接続したコネクションでも、相手から接続されたコネクションでも