订阅我们的博客

In the previous Translator 101 post, we looked at some of the dispatch tables and options processing in a translator. This time we’re going to cover the rest of the “shell” of a translator – i.e. the other global parts not specific to handling a particular request.

Let’s start by looking at the relationship between a translator and its shared library. At a first approximation, this is the relationship between an object and a class in just about any object-oriented programming language. The class defines behaviors, but has to be instantiated as an object to have any kind of existence. In our case the object is an xlator_t. Several of these might be created within the same daemon, sharing all of the same code through init/fini and dispatch tables, but sharing no data. You could implement shared data (as static variables in your shared libraries) but that’s strongly discouraged. Every function in your shared library will get an xlator_t as an argument, and should use it. This lack of class-level data is one of the points where the analogy to common OOP systems starts to break down. Another place is the complete lack of inheritance. Translators inherit behavior (code) from exactly one shared library – looked up and loaded using the “type” field in a volfile “volume . . . end-volume” block – and that’s it – not even single inheritance, no subclasses or superclasses, no mixins or prototypes, just the relationship between an object and its class. With that in mind, let’s turn to the init function that we just barely touched on last time.

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
 
int32_t
init (xlator_t *this)
{
        data_t *data = NULL;
        rot_13_private_t *priv = NULL;
 
        if (!this->children || this->children->next) {
                gf_log ("rot13", GF_LOG_ERROR,
                        "FATAL: rot13 should have exactly one child");
                return -1;
        }
 
        if (!this->parents) {
                gf_log (this->name, GF_LOG_WARNING,
                        "dangling volume. check volfile ");
        }
 
        priv = GF_CALLOC (sizeof (rot_13_private_t), 1, 0);
        if (!priv)
                return -1;

At the very top, we see the function signature – we get a pointer to the xlator_t object that we’re initializing, and we return an int32_t status. As with most functions in the translator API, this should be zero to indicate success. In this case it’s safe to return -1 for failure, but watch out: in dispatch-table functions, the return value means the status of the function call rather than the request. A request error should be reflected as a callback with a non-zero op_ret value, but the dispatch function itself should still return zero. In fact, the handling of a non-zero return from a dispatch function is not all that robust (we recently had a bug report in HekaFS related to this) so it’s something you should probably avoid altogether. This only underscores the difference between dispatch functions and init/fini functions, where non-zero returns are expected and handled logically by aborting the translator setup. We can see that down at the bottom, where we return -1 to indicate that we couldn’t allocate our private-data area (more about that later).

The first thing this init function does is check that the translator is being set up in the right kind of environment. Translators are called by parents and in turn call children. Some translators are “initial” translators that inject requests into the system from elsewhere – e.g. mount/fuse injecting requests from the kernel, protocol/server injecting requests from the network. Those translators don’t need parents, but rot-13 does and so we check for that. Similarly, some translators are “final” translators that (from the perspective of the current process) terminate requests instead of passing them on – e.g. protocol/client passing them to another node, storage/posix passing them to a local filesystem. Other translators “multiplex” between multiple children – passing each parent request on to one (cluster/dht), some (cluster/stripe), or all (cluster/afr) of those children. Rot-13 fits into none of those categories either, so it checks that it has exactly one child. It might be more convenient or robust if translator shared libraries had standard variables describing these requirements, to be checked in a consistent way by the translator-loading infrastructure itself instead of by each separate init function, but this is the way translators work today.

The last thing we see in this fragment is allocating our private data area. This can literally be anything we want; the infrastructure just provides the priv pointer as a convenience but takes no responsibility for how it’s used. In this case we’re using GF_CALLOC to allocate our own rot_13_private_t structure. This gets us all the benefits of GlusterFS’s memory-leak detection infrastructure, but the way we’re calling it is not quite ideal. For one thing, the first two arguments – from calloc(3) – are kind of reversed. For another, notice how the last argument is zero. That can actually be an enumerated value, to tell the GlusterFS allocator what type we’re allocating. This can be very useful information for memory profiling and leak detection, so it’s recommended that you follow the example of any xxx-mem-types.h file elsewhere in the source tree instead of just passing zero here (even though that works).

To finish our tour of standard initialization/termination, let’s look at the end of init and the beginning of fini

174
175
176
177
178
179
180
181
182
183
184
185
186
187
        this->private = priv;
        gf_log ("rot13", GF_LOG_DEBUG, "rot13 xlator loaded");
        return 0;
}
 
void
fini (xlator_t *this)
{
        rot_13_private_t *priv = this->private;
 
        if (!priv)
                return;
        this->private = NULL;
        GF_FREE (priv);

At the end of init we’re just storing our private-data pointer in the priv field of our xlator_t, then returning zero to indicate that initialization succeeded. As is usually the case, our fini is even simpler. All it really has to do is GF_FREE our private-data pointer, which we do in a slightly roundabout way here. Notice how we don’t even have a return value here, since there’s nothing obvious and useful that the infrastructure could do if fini failed.

That’s practically everything we need to know to get our translator through loading, initialization, options processing, and termination. If we had defined no dispatch functions, we could actually configure a daemon to use our translator and it would work as a basic pass-through from its parent to a single child. In the next post I’ll cover how to build the translator and configure a daemon to use it, so that we can actually step through it in a debugger and see how it all fits together before we actually start adding functionality.


关于作者

按频道浏览

automation icon

自动化

涵盖技术、团队和环境的最新自动化平台

AI icon

人工智能

平台更新使客户可以在任何地方运行人工智能工作负载

cloud services icon

云服务

有关我们的托管云服务组合的更多信息

security icon

安全防护

有关我们如何跨环境和技术减少风险的最新信息

edge icon

边缘计算

简化边缘运维的平台更新

Infrastructure icon

基础架构

全球领先企业 Linux 平台的最新动态

application development icon

应用领域

我们针对最严峻的应用挑战的解决方案

Original series icon

原创节目

关于企业技术领域的创客和领导者们有趣的故事