Skip to content

Instantly share code, notes, and snippets.

@EshaMaharishi
Last active October 1, 2024 12:45
Show Gist options
  • Save EshaMaharishi/962ac67912d38a4a3a048fe757db43e8 to your computer and use it in GitHub Desktop.
Save EshaMaharishi/962ac67912d38a4a3a048fe757db43e8 to your computer and use it in GitHub Desktop.
Decorations in MongoDB

The Decorator Pattern

A Decoration is an object that is stored as part of another object, called the Decorable.

The lifetime of the Decoration is tied to the lifetime of the Decorable. This means the Decoration is constructed when the Decorable's constructor is called, and the Decoration is destructed when the Decorable's destructor is called.

Finally, the decoration can only be accessed through the decorable.

The Decorator pattern is an easy way to add parts to an existing object without changing the object.

Decorations in MongoDB

We'll start with an example of a Decorable object and a Decoration on it.

Then we'll explain the internals of the Decorable class.

How Decorations Are Used: An Example

MongoDB's codebase implements the decorator pattern through its own Decorable class.

For this example, we'll use

Any class that extends Decorable can have decorations added to it. For example, Client extends Decorable:

client.h:64

/**
 * The database's concept of an outside "client".
 * */
class Client final : public Decorable<Client> {
public:

The Decorable class has a static declareDecoration() method that can be used to declare other classes as decorations on the Decorable:

decorable.h:103-106

    template <typename T>
    static Decoration<T> declareDecoration();

For example, the ClusterLastErrorInfo class is declared as a decoration on Client by calling Client::declareDecoration<ClusterLastErrorInfo>:

cluster_last_error_info.h:44

class ClusterLastErrorInfo {
public:
    static const Client::Decoration<ClusterLastErrorInfo> get;

cluster_last_error_info.cpp:38-39

const Client::Decoration<ClusterLastErrorInfo> ClusterLastErrorInfo::get =
    Client::declareDecoration<ClusterLastErrorInfo>();

Note, get is not a function! (The variable get could have been more aptly named something like 'decorationHandle').

This static "handle" can be used to obtain an actual instance of a Decoration by passing it a particular instance of the Decorable, via Decoration's parenthesis operator:

decorable.h:78-80

    class Decoration { // the "handle" class
    public:
    T& operator()(D& d) const;

Let's look at an example.

This parenthesis operator is used on the ClusterLastErrorInfo::get variable in mongos's getLastError command.

A particular Client (obtained through cc()), is passed as an argument to the parenthesis operator:

cluster_get_last_error_cmd.cpp:244

    const HostOpTimeMap hostOpTimes(ClusterLastErrorInfo::get(cc()).getPrevHostOpTimes());

The particular ClusterLastErrorInfo associated with that particular Client is obtained, and getPrevHostOpTimes() is called on it.

Decorable Internals

Now we'll look at how Decorable objects in MongoDB store, construct, and destruct their decorations.

This will explain the two methods we glossed over above, Decorable::declareDecoration() and Decoration<T>::operator()(D& d).

How Decorable is Implemented

Each instance of a Decorable has a DecorationContainer that has the actual storage space for its decorations (for example, remember that each Client has its own ClusterLastErrorInfo).

Each Decorable also has a static DecorationRegistry that assigns an index into the storage space for each decoration that is declared through Decorable::declareDecoration().

The registry and indices are static, because a particular Decoration always has the same index in the list of decorations on a particular Decorable. (That is, ClusterLastErrorInfo always has the same index in the storage space on a Client).

The DecorationContainer: The Storage Space

Each Decorable has a private DecorationContainer:

decorable.h:118

    DecorationContainer _decorations;

The DecorationContainer's field _decorationData contains the storage space:

decoration_container.h:126-127

    const std::unique_ptr<unsigned char[]> _decorationData;

And DecorationContainer has methods to return a Decoration by its index into the storage space:

decoration_container.h

    void* getDecoration(DecorationDescriptor descriptor) {
        return _decorationData.get() + descriptor._index;
    }

The DecorationRegistry: Declaring a Decoration

Each Decorable also has a static DecorationRegistry for assigning the indices:

decorable.h:113-116

    static DecorationRegistry* getRegistry() {
        static DecorationRegistry* theRegistry = new DecorationRegistry();
        return theRegistry;
    }

In the DecorationRegistry, each decoration's index, along with wrappers around its constructor and destructor, is stored in a DecorationInfo struct:

decoration_registry.h:99-108

    struct DecorationInfo {
        DecorationInfo() {}
        DecorationInfo(DecorationContainer::DecorationDescriptor descriptor,
                       DecorationConstructorFn constructor,
                       DecorationDestructorFn destructor);

        DecorationContainer::DecorationDescriptor descriptor;
        DecorationConstructorFn constructor;
        DecorationDestructorFn destructor;
    };

The wrapper methods are straightforward; they just allow the decoration to be constructed at a specific location, so that they can be constructed in the storage space:

decoration_registry.h:112-115

    template <typename T>
    static void constructAt(void* location) {
        new (location) T();
    }

Let's now look at Decorable::declareDecoration() more closely:

decorable.h:103-106

    template <typename T>
    static Decoration<T> declareDecoration() {
        return Decoration<T>(getRegistry()->declareDecoration<T>());
    }

It calls the DecorationRegistry's declareDecoration() method.

This method describes the size of the decoration and passes the wrapper methods:

decoration_registry.h:61-67

    template <typename T>
    DecorationContainer::DecorationDescriptorWithType<T> declareDecoration() {
        MONGO_STATIC_ASSERT_MSG(std::is_nothrow_destructible<T>::value,
                                "Decorations must be nothrow destructible");
        return DecorationContainer::DecorationDescriptorWithType<T>(std::move(declareDecoration(
            sizeof(T), std::alignment_of<T>::value, &constructAt<T>, &destructAt<T>)));
    }

The DecorationRegistry's private helper method for declareDecoration() takes those size requirements and assigns the decoration its index in the storage space:

decoration_registry.cpp:35-48

DecorationContainer::DecorationDescriptor DecorationRegistry::declareDecoration(
    const size_t sizeBytes,
    const size_t alignBytes,
    const DecorationConstructorFn constructor,
    const DecorationDestructorFn destructor) {
    const size_t misalignment = _totalSizeBytes % alignBytes;
    if (misalignment) {
        _totalSizeBytes += alignBytes - misalignment;
    }
    DecorationContainer::DecorationDescriptor result(_totalSizeBytes);
    _decorationInfo.push_back(DecorationInfo(result, constructor, destructor));
    _totalSizeBytes += sizeBytes;
    return result;
}

So, when Client::declareDecoration<ClusterLastErrorInfo>() is called (at program startup time), an index for ClusterLastErrorInfo is assigned into the storage space of all Client's.

The index is returned and stored into the static const member variable get of ClusterLastErrorInfo.

How the "handle" is used to obtain a Decoration from a Decorable

Let's now look more closely at the second method we glossed over, T& Decoration::operator()(D& d) const:

decorable.h:78-80

    class Decoration {
    public:
    T& operator()(D& d) const {
        return static_cast<Decorable&>(d)._decorations.getDecoration(_raw);
    }
    ...
    DecorationContainer::DecorationDescriptorWithType<T> _raw;

The index returned by declareDecoration() (e.g., what was stored in ClusterLastErrorInfo::get) was of type Decoration, so it has this parenthesis operator.

This parenthesis operator takes a particular instance of the Decorable, reaches into the Decorable's storage space _decorations, and using its stored assigned index (in _raw), pulls out the particular decoration for the particular Decorable.

How the Decorations are Constructed/Destructed

Notice that declaring the decoration does not actually construct an instance of the decoration.

This is because all of the decorations are constructed in one go, through the DecorationRegistry::construct() method, by iterating over all the DecorationInfo's and calling their constructors:

void DecorationRegistry::construct(DecorationContainer* decorable) const {
    auto iter = _decorationInfo.cbegin();
    try {
        for (; iter != _decorationInfo.cend(); ++iter) {
            iter->constructor(decorable->getDecoration(iter->descriptor));
        }
    } catch (...) {
        try {
            while (iter != _decorationInfo.cbegin()) {
                --iter;
                iter->destructor(decorable->getDecoration(iter->descriptor));
            }
        } catch (...) {
            std::terminate();
        }
        throw;
    }
}

This overarching DecorationRegistry::construct() method is called by the DecorationContainer's constructor, which passes itself as the argument:

decoration_container.cpp:37-41

DecorationContainer::DecorationContainer(const DecorationRegistry* registry)
    : _registry(registry),
      _decorationData(new unsigned char[registry->getDecorationBufferSizeBytes()]) {
    _registry->construct(this);
}

This is how the decorations get constructed in the Decorable's storage space.

So, when a Client is constructed, its DecorationContainer's constructor is called, which calls DecorationRegistry::construct(), which calls the constructors of all the decorations that have been declared.

(If you declare a primitive as a decoration, it will be initialized to the zero/false/null value, since the decorations go through C++ value initialization).

@baiwfg2
Copy link

baiwfg2 commented Jul 20, 2019

Really messy to me. For the moment I have no idea whether this design make sense. Anyway, thanks for sharing.

@EshaMaharishi
Copy link
Author

EshaMaharishi commented Jul 20, 2019 via email

@drusong
Copy link

drusong commented Feb 18, 2022

hell too complicated, yet sometimes useful, thanks for these detailed explanations of MongoDB source code

@EshaMaharishi
Copy link
Author

@drusong , glad it was helpful. Thanks for commenting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment