Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 83 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ find_library(LIBLOGOSDELIVERY_PATH NAMES ${LIBLOGOSDELIVERY_NAMES} PATHS ${LIBLO

# Plugin sources
set(PLUGIN_SOURCES
delivery_module_plugin.cpp
delivery_module_plugin.h
delivery_module_interface.h
src/delivery_module_plugin.cpp
src/delivery_module_plugin.h
src/delivery_module_interface.h
)

# Add liblogos interface header
Expand Down Expand Up @@ -203,7 +203,7 @@ endif()

# Include directories
target_include_directories(delivery_module_plugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/src
${LIBLOGOSDELIVERY_DIR}
)

Expand Down Expand Up @@ -300,3 +300,82 @@ install(DIRECTORY "${PLUGINS_OUTPUT_DIR}/"
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-delivery-module/generated
OPTIONAL
)

include(CTest)

if(BUILD_TESTING)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Test)

add_executable(delivery_module_plugin_integration_test
test/test_create_node_integration.cpp
src/delivery_module_plugin.cpp
src/delivery_module_plugin.h
src/delivery_module_interface.h
src/api_call_handler.h
src/QExpected.h
)

target_include_directories(delivery_module_plugin_integration_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

if(EXISTS "${LOGOS_DELIVERY_ROOT}/include/liblogosdelivery.h")
target_include_directories(delivery_module_plugin_integration_test PRIVATE ${LOGOS_DELIVERY_ROOT}/include)
elseif(EXISTS "${LOGOS_DELIVERY_ROOT}/liblogosdelivery/liblogosdelivery.h")
target_include_directories(delivery_module_plugin_integration_test PRIVATE ${LOGOS_DELIVERY_ROOT}/liblogosdelivery)
endif()

if(_liblogos_is_source)
target_include_directories(delivery_module_plugin_integration_test PRIVATE ${LOGOS_LIBLOGOS_ROOT})
else()
target_include_directories(delivery_module_plugin_integration_test PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
endif()

if(_cpp_sdk_is_source)
target_include_directories(delivery_module_plugin_integration_test PRIVATE
${LOGOS_CPP_SDK_ROOT}/cpp
${LOGOS_CPP_SDK_ROOT}/cpp/generated
)
else()
target_include_directories(delivery_module_plugin_integration_test PRIVATE
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
)
endif()

target_link_libraries(delivery_module_plugin_integration_test PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::RemoteObjects
Qt${QT_VERSION_MAJOR}::Test
)

if(NOT _cpp_sdk_is_source)
target_link_libraries(delivery_module_plugin_integration_test PRIVATE ${LOGOS_SDK_LIB})
else()
target_sources(delivery_module_plugin_integration_test PRIVATE
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.cpp
)
endif()

if(LIBLOGOSDELIVERY_PATH)
target_link_libraries(delivery_module_plugin_integration_test PRIVATE ${LIBLOGOSDELIVERY_PATH})

if(APPLE)
add_custom_command(TARGET delivery_module_plugin_integration_test POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DTEST_BIN=$<TARGET_FILE:delivery_module_plugin_integration_test>
-DREAL_LIB=${LIBLOGOSDELIVERY_PATH}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/fix_liblogosdelivery_id.cmake
COMMENT "Rewriting liblogosdelivery install name for integration test runtime"
)
endif()
endif()

add_test(NAME delivery_module_plugin_integration_test COMMAND delivery_module_plugin_integration_test)
endif()
14 changes: 6 additions & 8 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# Common CMake flags
cmakeFlags = [
"-GNinja"
"-DBUILD_TESTING=ON"
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
"-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}"
"-DLOGOS_DELIVERY_ROOT=${logosDelivery}"
Expand Down
7 changes: 7 additions & 0 deletions nix/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ pkgs.stdenv.mkDerivation {
inherit src;
inherit (common) nativeBuildInputs buildInputs cmakeFlags meta env;

doCheck = true;
checkPhase = ''
runHook preCheck
ctest --output-on-failure
runHook postCheck
'';

# Determine platform-specific library extension
libdeliveryLib = if pkgs.stdenv.hostPlatform.isDarwin then "liblogosdelivery.dylib" else "liblogosdelivery.so";
libpqPattern = if pkgs.stdenv.hostPlatform.isDarwin then "libpq*.dylib" else "libpq.so*";
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
104 changes: 72 additions & 32 deletions delivery_module_plugin.cpp → src/delivery_module_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <memory>
#include <mutex>
#include <semaphore>
#include <unordered_map>

#include "api_call_handler.h"
// Include the liblogosdelivery header from logos-delivery
Expand Down Expand Up @@ -116,61 +119,98 @@ void DeliveryModulePlugin::initLogos(LogosAPI* logosAPIInstance) {

bool DeliveryModulePlugin::createNode(const QString &cfg)
{
std::lock_guard<std::mutex> createNodeLock(createNodeMutex);

if (deliveryCtx != nullptr) {
qWarning() << "DeliveryModulePlugin: createNode rejected - context already initialized";
return false;
}

qDebug() << "DeliveryModulePlugin::createNode called with cfg:" << cfg;

// Convert QString to UTF-8 byte array
QByteArray cfgUtf8 = cfg.toUtf8();

// Create semaphore and callback context for synchronous operation
// Callback is only called in failure case
// Create callback context for synchronous createNode result.
// The context is kept in a pending map so late callbacks can be safely ignored.
struct CallbackContext {
std::binary_semaphore* sem;
bool callbackInvoked;
std::binary_semaphore sem{0};
int callerRet{RET_ERR};
QString message;
};

static std::mutex pendingMutex;
static std::unordered_map<void*, std::shared_ptr<CallbackContext>> pendingContexts;

auto callbackCtx = std::make_shared<CallbackContext>();
void* callbackKey = static_cast<void*>(callbackCtx.get());

{
std::lock_guard<std::mutex> lock(pendingMutex);
pendingContexts[callbackKey] = callbackCtx;
}

std::binary_semaphore sem(0);
CallbackContext ctx{&sem, false};

// Lambda callback that will be called only on failure (when deliveryCtx is nullptr)
// Callback is expected in both success and error cases.
auto callback = +[](int callerRet, const char* msg, size_t len, void* userData) {
qDebug() << "DeliveryModulePlugin::createNode callback called with ret:" << callerRet;

CallbackContext* ctx = static_cast<CallbackContext*>(userData);
if (!ctx) {
qWarning() << "DeliveryModulePlugin::createNode callback: Invalid userData";

std::shared_ptr<CallbackContext> callbackCtx;
{
std::lock_guard<std::mutex> lock(pendingMutex);
auto it = pendingContexts.find(userData);
if (it == pendingContexts.end()) {
return;
}
callbackCtx = it->second;
pendingContexts.erase(it);
}

if (!callbackCtx) {
return;
}


callbackCtx->callerRet = callerRet;
if (msg && len > 0) {
QString message = QString::fromUtf8(msg, len);
qDebug() << "DeliveryModulePlugin::createNode callback message:" << message;
callbackCtx->message = QString::fromUtf8(msg, len);
qDebug() << "DeliveryModulePlugin::createNode callback message:" << callbackCtx->message;
}

ctx->callbackInvoked = true;


// Release semaphore to unblock the createNode method
ctx->sem->release();
callbackCtx->sem.release();
};

// Call logosdelivery_create_node with the configuration
// Important: Keep deliveryCtx assignment from the call
deliveryCtx = logosdelivery_create_node(cfgUtf8.constData(), callback, &ctx);

// If deliveryCtx is nullptr, callback will be invoked with error details
if (!deliveryCtx) {
qDebug() << "DeliveryModulePlugin: Waiting for createNode error callback...";

// Wait for callback to complete with timeout
if (!sem.try_acquire_for(CALLBACK_TIMEOUT)) {
qWarning() << "DeliveryModulePlugin: Timeout waiting for createNode callback";
return false;
// Important: Keep deliveryCtx assignment from the call,
// creating of the context is immediate and not depends on the callback.
deliveryCtx = logosdelivery_create_node(cfgUtf8.constData(), callback, callbackKey);

qDebug() << "DeliveryModulePlugin: Waiting for createNode callback...";

// Wait for callback result regardless of immediate pointer value.
// Callback ensures that the underlying node object is properly created.
if (!callbackCtx->sem.try_acquire_for(CALLBACK_TIMEOUT)) {
std::lock_guard<std::mutex> lock(pendingMutex);
pendingContexts.erase(callbackKey);

deliveryCtx = nullptr;

qWarning() << "DeliveryModulePlugin: Timeout waiting for createNode callback";
return false;
}

// Any issue happened during node creation means the context is destroyed and must not be user.
if (callbackCtx->callerRet != RET_OK || deliveryCtx == nullptr) {
if (!callbackCtx->message.isEmpty()) {
qWarning() << "DeliveryModulePlugin: createNode callback error:" << callbackCtx->message;
}


deliveryCtx = nullptr;

qWarning() << "DeliveryModulePlugin: Failed to create Messaging context";
return false;
}

// Success case - deliveryCtx is valid, callback won't be called
// Success case - deliveryCtx is valid and callback returned RET_OK.
qDebug() << "DeliveryModulePlugin: Messaging context created successfully";

// Set up event callback
Expand Down
4 changes: 3 additions & 1 deletion delivery_module_plugin.h → src/delivery_module_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

#include <QtCore/QObject>
#include <chrono>
#include <mutex>
#include "delivery_module_interface.h"
#include "logos_api.h"
#include "logos_api_client.h"

class DeliveryModulePlugin : public QObject, public DeliveryModuleInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID DeliveryModuleInterface_iid FILE "metadata.json")
Q_PLUGIN_METADATA(IID DeliveryModuleInterface_iid FILE "../metadata.json")
Q_INTERFACES(DeliveryModuleInterface PluginInterface)

public:
Expand All @@ -34,6 +35,7 @@ class DeliveryModulePlugin : public QObject, public DeliveryModuleInterface

private:
void* deliveryCtx;
std::mutex createNodeMutex;

// Timeout for callback operations
static constexpr std::chrono::seconds CALLBACK_TIMEOUT{30};
Expand Down
Loading