ceph RBD中用到的回调函数和回调类

在看rbd-mirror的代码中,出现了以下的代码逻辑:

template <typename I>
void ImageReplayer<I>::wait_for_deletion() {
  dout(20) << dendl;

  Context *ctx = create_context_callback<
    ImageReplayer, &ImageReplayer<I>::handle_wait_for_deletion>(this);
  m_image_deleter->wait_for_scheduled_deletion(
    m_local_pool_id, m_global_image_id, ctx, false);
}

template <typename I>
void ImageReplayer<I>::handle_wait_for_deletion(int r) {
  dout(20) << "r=" << r << dendl;

  if (r == -ECANCELED) {
    on_start_fail(0, "");
    return;
  } else if (r < 0) {
    on_start_fail(r, "error waiting for image deletion");
    return;
  }

  prepare_local_image();
}
template <typename I>
void ImageReplayer<I>::prepare_local_image() {
  dout(20) << dendl;

  m_local_image_id = "";
  Context *ctx = create_context_callback<
    ImageReplayer, &ImageReplayer<I>::handle_prepare_local_image>(this);
  auto req = PrepareLocalImageRequest<I>::create(
    m_local_ioctx, m_global_image_id, &m_local_image_id, &m_local_image_name,
    &m_local_image_tag_owner, m_threads->work_queue, ctx);
  req->send();
}

template <typename I>
void ImageReplayer<I>::handle_prepare_local_image(int r) {
  dout(20) << "r=" << r << dendl;

  if (r == -ENOENT) {
    dout(20) << "local image does not exist" << dendl;
  } else if (r < 0) {
    on_start_fail(r, "error preparing local image for replay");
    return;
  } else {
    reregister_admin_socket_hook();
  }

  // local image doesn't exist or is non-primary
  prepare_remote_image();
}

首先说明下以上代码的逻辑,以prepare_local_image为例,在这个函数中,使用了create_context_callback注册了一个回调函数handle_prepare_local_image(后面可以看到很多以handle开头的函数,都可以视为回调函数),注册好了后就继续执行req->send();send()函数返回时,那么就会调用handle_prepare_local_image函数了,接着执行handle_prepare_local_image中的逻辑。以此类推,如果进入到req->send(),可以发现在send()函数中也是有同样的逻辑(即就是注册回调函数,然后执行一个任务,执行结束后调用回调函数)

下面再来详细分析。

要看懂这两部分的代码逻辑需要对ceph rbd中的回调函数和回调类有所了解。

  1. 回调类和回调函数
    这里需要着重关注的是create_context_callback这个函数。
    原型位于src/librbd/Utils.h:
template <typename T, void(T::*MF)(int) = &T::complete>
Context *create_context_callback(T *obj) {
  return new detail::C_CallbackAdapter<T, MF>(obj);
}

还会牵涉到一个回调函数适配器C_CallbackAdapter

namespace detail
{
    ........
    template <typename T, void (T::*MF)(int)>
    class C_CallbackAdapter : public Context {
    T *obj;
        public:
            C_CallbackAdapter(T *obj) : obj(obj) {
     }
 
        protected:
           void finish(int r) override {
           (obj->*MF)(r);
           }
     };
}

C_CallbackAdapter类型继承自Context类型。同时,重载了finish()函数。
Context类定义在src/include文件中,该类是一个回调函数类的抽象类,继承它的类只要在子类实现它的finish函数,在finish函数调用自己需要回调的函数,就可以完成回调。

class Context {
    Context(const Context& other);
    const Context& operator=(const Context& other);
protected:
    virtual void finish(int r) = 0;
public:
    Context() {}
    virtual ~Context() {}       
    // we want a virtual destructor!!!
    virtual void complete(int r) {
    finish(r);
    delete this;
    }
};

看到这里大概就会明白文章开头的rbd-mirror这段代码的意思了:

Context *ctx = create_context_callback<
    ImageReplayer, &ImageReplayer<I>::handle_prepare_local_image>(this);

通过create_context_callback<template T>(*obj)这个模板函数,将ImageReplayer这个类转换成了一个回调类,同时设置了回调函数就是handle_prepare_local_image。其他的也是类似的。


下面来写个demo,剥离掉一些无用的,看看回调类和回调函数的基本,核心实现。

#include <iostream>
#include <string>
#include <errno.h>
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <assert.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h>
#include <vector>
#include <list>
using namespace std;

//Base class and function
class Context {
  Context(const Context& other);
  const Context& operator=(const Context& other);

  protected:
   virtual void finish(int r) = 0;

  public:
   Context() {}
   virtual ~Context() {}
   virtual void complete(int r) {
     finish(r);
     delete this;
   }
};
template <typename T, void (T::*MF)(int)>
class C_CallbackAdapter: public Context {
  T *obj;
public:
  C_CallbackAdapter(T *obj) : obj(obj) {
  }
protected:
  void finish(int r) override {
    (obj->*MF)(r);
  }
};


template <typename T, void(T::*MF)(int) = &T::complete>
Context *create_context_callback(T *obj) {
    return new C_CallbackAdapter<T, MF>(obj);
}

class TestCallback {

  public:
  protected:
  public:
    void start_test();
    Context *pre_set_fn();
    void handle_test_callback(int r);
  private:
};

Context *TestCallback::pre_set_fn() {
  Context *ctx = create_context_callback<
    TestCallback, &TestCallback::handle_test_callback>(this);

  return ctx;

}
void TestCallback::handle_test_callback(int r)
{
  std::cout<<"This printed by <handle_test_callback>"<<std::endl;
}
int main()
{
  TestCallback *test_callback = new TestCallback;
  Context *ctx = test_callback->pre_set_fn();
  int r = 0;
  ctx->complete(r);

  return 0;
}

编译和运行:

g++ -std=c++11 callback_class_function.cc -o test_callback
./test_callback

通过以上的模拟代码可以看出,当在main()中调用了complete()函数后,就会间接调用到类中实现的handle_xxxx函数以模拟回调的功能。
另外,还可以在ceph 的class Finsisher类中看到对complete()函数的调用。
Finisher类是在src/common中定义的一个专门查看操作是否结束的一个类。在这个类里面拥有一个线程finisher_thread和一个类型为Context指针的队列finisher_queue。当一个操作线程完成自己的操作后,会将Context类型对象送入队列。此时finisher_thread线程循环监视着自己的finisher_queue队列,当发现了有新进入的Context时,会调用这个Context::complete函数,这个函数则会调用到Context子类自己实现的finish()函数。来处理操作完成后的后续工作。

class Finisher {
    CephContext *cct;
    ……
    vector<Context*> finisher_queue;
    ……
    void *finisher_thread_entry();
    struct FinisherThread : public Thread {
        Finisher *fin;    
        FinisherThread(Finisher *f) : fin(f) {}
        void* entry() { return (void*)fin->finisher_thread_entry(); }
    } finisher_thread;
    ……
}
 
void *Finisher::finisher_thread_entry()
{
    ……
    while(!finisher_stop){
        while(!finisher_queue.empty()){
            ……
            vector<Context*> ls
            ls.swap(finisher_queue);
            for (vector<Context*>::iterator p = ls.begin();
	          p != ls.end();
	          ++p) {
	            if (*p) {
		            //这里面调用Context子类实现的finish函数
	                (*p)->complete(0);
	            }
            }
        }
    }
}

既然这里提到了,再来写一个demo,来尝试使用下class Finisher类处理回调函数。

posted @ 2020-02-21 17:52  Linux-inside  阅读(655)  评论(0编辑  收藏  举报