Implement versioned extension registration helpers in C++ SDK

XMLWordPrintableJSON

    • Type: Task
    • Resolution: Fixed
    • Priority: Major - P3
    • 8.3.0-rc0
    • Affects Version/s: None
    • Component/s: None
    • Query Integration
    • Fully Compatible
    • None
    • 3
    • TBD
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      As part of  https://github.com/10gen/mongo/pull/39882 , the C++ sdk extension registration code was improved, providing helper macros for extension developers to register their extensions with little effort.

      However, during the review process, we found that the solution we'd arrived at did not properly accommodate scenarios in which extension developers may have two versions of the extension, and would need to return a specific version based on the host's supported versions.

      The current macros offered by the SDK can't be used at all in those cases as can be seen in hostVersionSucceeds.cpp .

      What we probably want is something like the proposal below, which would allow developers to register versions of their extension, and obfuscate the version negotiation logic into the SDK.

       
      struct VersionedExtension {
        Version version;
        std::function<> getter;
      };
      class VersionedExtensionContainer {
      public:
          VersionedExtensionContainer& getInstance() {
               static VersionedExtensionContainer singleton;
               return singleton;
          } 
          
          void registerVersion(Version, std::function<>) {
              //insert functor into map with associated version.
              // Make sure no duplicates.
              // Make sure to insert in descending order (highest version first)
          }
          
          std::unique_ptr<sdk::Extension> getVersionedExtension(APIVersionVector hostVersions) {
                    for (auto versionedExtension : versionedExtensions) {
                        if (sdk::isVersionCompatible(hostVersions, versionedExtension.version) {
                               return std::make_unique<sdk::ExtensionAdapter>(versionedExtension.getter());
                        }
                    }
                    return nullptr;
          }
      private:
        // use std::list, since we are unlikely to need a large number of versions.
        // We should maintain this list as ORDERED in descending order (i.e highest version first).
       std::list<VersionedExtension> versionedExtensions; 
      };
      

       

      We then provide the REGISTER_EXTENSION macro:

      #define REGISTER_EXTENSION_WITH_VERSION(Version, My_CPP_Versioned_Getter) 
      auto& versionedExtensions = VersionedExtensionContainer::getInstance();
      versionedExtensions.registerVersion(Version, []() {return MY_CPP_Versioned_Getter(); });

       

      Then an extension developer might do something like this :

       

      std::unique_ptr<sdk::Extension>  get_my_extension_cpp_version1(const MongoExtensionHostPortal* portal) {
           return std::make_unique<MyExtensionV1>();
      }
      std::unique_ptr<sdk::Extension>  get_my_extension_cpp_version2(const MongoExtensionHostPortal* portal) {
           return std::make_unique<MyExtensionV2>();
      }
      REGISTER_EXTENSION_WITH_VERSION(MyExtensionV1, get_my_extension_cpp_version1, apiVersion1);
      REGISTER_EXTENSION_WITH_VERSION(MyExtensionV2, get_my_extension_cpp_version2, apiVersion2);
      DEFINE_GET_EXTENSION()
      

      Where DEFINE_GET_EXTENSION looks like this:

       

      /**
       * Base case macro to define get_mongodb_extension.
       */
      #define DEFINE_GET_EXTENSION()                         \
          extern "C" {                                                          
          ::MongoExtensionStatus* get_mongodb_extension(                    \
              const ::MongoExtensionAPIVersionVector* hostVersions,       \
              const ::MongoExtension** extension) {                        \
              return mongo::extension::sdk::enterCXX([&] {             \
                  const auto& versionedExtensionContainer = VersionedExtensionContainer.getInstance();
                  static auto extensionPtr = versionedExtensionContainer.getVersionedExtension(hostVersions);
                  *extension = reinterpret_cast<const ::MongoExtension*>(extensionPtr.get());           \
              });                   \
          }                          \
          }

       

       

      Alternatively, we could simplify the above design by mandating that extension developers have to provide a static method create() on their extension implementation. 

       

       

      class MyExtensionV1 : sdk::Extension{
         static std::unique_ptr<sdk::Extension> create() {
             return std::make_unique<MyExtensionV1>();
          };
      };
      
      class MyExtensionV2 : sdk::Extension{
         static std::unique_ptr<sdk::Extension> create() {
             return std::make_unique<MyExtensionV2>();
          };
         };
      
      
      REGISTER_EXTENSION_WITH_VERSION(MyExtensionV1, apiVersion1);
      REGISTER_EXTENSION_WITH_VERSION(MyExtensionV2, apiVersion2); 

       

       

      Where REGISTER_EXTENSION_WITH_VERSION is defined as :

      #define REGISTER_EXTENSION_WITH_VERSION(Extension, Version) \ 
      auto& versionedExtensions = VersionedExtensionContainer::getInstance();
      versionedExtensions.registerVersion(Version, []() {return Extension::create(); });

      Link to original github PR discussion: https://github.com/10gen/mongo/pull/39882#discussion_r2273895517

            Assignee:
            Josh Siegel
            Reporter:
            Santiago Roche
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: