Log in / Register Account

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.


About the author

Of interest

News to note—just for you