# HG changeset patch # User Sebastien Jodogne # Date 1440680338 -7200 # Node ID e1e54a73ba8bd6fd399e62a80139459f96e15339 # Parent 2bac60a4f584701e4c740dc45a5d898f44552aab OrthancPluginRegisterRestCallbackNoLock, documentation diff -r 2bac60a4f584 -r e1e54a73ba8b NEWS --- a/NEWS Thu Aug 27 12:56:48 2015 +0200 +++ b/NEWS Thu Aug 27 14:58:58 2015 +0200 @@ -25,6 +25,7 @@ * New function "OrthancPluginWriteFile()" to write files to the filesystem * New function "OrthancPluginGetErrorDescription()" to convert error codes to strings * New function "OrthancPluginSendHttpStatus()" to send HTTP status with a body +* New function "OrthancPluginRegisterRestCallbackNoLock()" for high-performance plugins * Plugins have access to explicit error codes Maintenance diff -r 2bac60a4f584 -r e1e54a73ba8b Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu Aug 27 12:56:48 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Aug 27 14:58:58 2015 +0200 @@ -113,10 +113,56 @@ struct OrthancPlugins::PImpl { - typedef std::pair Property; + class RestCallback : public boost::noncopyable + { + private: + boost::regex regex_; + OrthancPluginRestCallback callback_; + bool lock_; + + int32_t InvokeInternal(HttpOutput& output, + const std::string& flatUri, + const OrthancPluginHttpRequest& request) + { + return callback_(reinterpret_cast(&output), + flatUri.c_str(), + &request); + } - typedef std::pair RestCallback; - typedef std::list RestCallbacks; + public: + RestCallback(const char* regex, + OrthancPluginRestCallback callback, + bool lockRestCallbacks) : + regex_(regex), + callback_(callback), + lock_(lockRestCallbacks) + { + } + + const boost::regex& GetRegularExpression() const + { + return regex_; + } + + int32_t Invoke(boost::recursive_mutex& restCallbackMutex, + HttpOutput& output, + const std::string& flatUri, + const OrthancPluginHttpRequest& request) + { + if (lock_) + { + boost::recursive_mutex::scoped_lock lock(restCallbackMutex); + return InvokeInternal(output, flatUri, request); + } + else + { + return InvokeInternal(output, flatUri, request); + } + } + }; + + typedef std::pair Property; + typedef std::list RestCallbacks; typedef std::list OnStoredCallbacks; typedef std::list OnChangeCallbacks; typedef std::map Properties; @@ -204,8 +250,7 @@ for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin(); it != pimpl_->restCallbacks_.end(); ++it) { - // Delete the regular expression associated with this callback - delete it->first; + delete *it; } } @@ -255,7 +300,7 @@ size_t bodySize) { std::string flatUri = Toolbox::FlattenUri(uri); - OrthancPluginRestCallback callback = NULL; + PImpl::RestCallback* callback = NULL; std::vector groups; std::vector cgroups; @@ -268,9 +313,9 @@ // Check whether the regular expression associated to this // callback matches the URI boost::cmatch what; - if (boost::regex_match(flatUri.c_str(), what, *(it->first))) + if (boost::regex_match(flatUri.c_str(), what, (*it)->GetRegularExpression())) { - callback = it->second; + callback = *it; // Extract the value of the free parameters of the regular expression if (what.size() > 1) @@ -283,13 +328,12 @@ cgroups[i - 1] = groups[i - 1].c_str(); } } - - found = true; } } - if (!found) + if (callback == NULL) { + // Callback not found return false; } @@ -346,14 +390,7 @@ } assert(callback != NULL); - int32_t error; - - { - boost::recursive_mutex::scoped_lock lock(pimpl_->restCallbackMutex_); - error = callback(reinterpret_cast(&output), - flatUri.c_str(), - &request); - } + int32_t error = callback->Invoke(pimpl_->restCallbackMutex_, output, flatUri, request); if (error == 0 && output.IsWritingMultipart()) @@ -459,13 +496,18 @@ } - void OrthancPlugins::RegisterRestCallback(const void* parameters) + void OrthancPlugins::RegisterRestCallback(const void* parameters, + bool lock) { const _OrthancPluginRestCallback& p = *reinterpret_cast(parameters); - LOG(INFO) << "Plugin has registered a REST callback on: " << p.pathRegularExpression; - pimpl_->restCallbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback)); + LOG(INFO) << "Plugin has registered a REST callback " + << (lock ? "with" : "witout") + << " mutual exclusion on: " + << p.pathRegularExpression; + + pimpl_->restCallbacks_.push_back(new PImpl::RestCallback(p.pathRegularExpression, p.callback, lock)); } @@ -995,7 +1037,11 @@ return true; case _OrthancPluginService_RegisterRestCallback: - RegisterRestCallback(parameters); + RegisterRestCallback(parameters, true); + return true; + + case _OrthancPluginService_RegisterRestCallbackNoLock: + RegisterRestCallback(parameters, false); return true; case _OrthancPluginService_RegisterOnStoredInstanceCallback: diff -r 2bac60a4f584 -r e1e54a73ba8b Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Thu Aug 27 12:56:48 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu Aug 27 14:58:58 2015 +0200 @@ -56,7 +56,8 @@ void CheckContextAvailable(); - void RegisterRestCallback(const void* parameters); + void RegisterRestCallback(const void* parameters, + bool lock); void RegisterOnStoredInstanceCallback(const void* parameters); diff -r 2bac60a4f584 -r e1e54a73ba8b Plugins/Include/orthanc/OrthancCDatabasePlugin.h --- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Thu Aug 27 12:56:48 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Thu Aug 27 14:58:58 2015 +0200 @@ -1,5 +1,5 @@ /** - * @defgroup CInterface C Interface + * @ingroup CInterface **/ /** @@ -47,9 +47,14 @@ { #endif + + /** + * Opaque structure that represents the context of a custom database engine. + **/ typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; +//! @cond Doxygen_Suppress typedef enum { _OrthancPluginDatabaseAnswerType_None = 0, @@ -624,7 +629,7 @@ void* payload); } OrthancPluginDatabaseBackend; - +//! @endcond typedef struct @@ -634,6 +639,19 @@ void* payload; } _OrthancPluginRegisterDatabaseBackend; + /** + * Register a custom database back-end. + * + * Instead of manually filling the OrthancPluginDatabaseBackend + * structure, you should instead implement a concrete C++ class + * deriving from ::OrthancPlugins::IDatabaseBackend, and register it + * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @return The context of the database engine (it must not be manually freed). + **/ ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackend( OrthancPluginContext* context, const OrthancPluginDatabaseBackend* backend, @@ -665,7 +683,6 @@ } - #ifdef __cplusplus } #endif diff -r 2bac60a4f584 -r e1e54a73ba8b Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu Aug 27 12:56:48 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu Aug 27 14:58:58 2015 +0200 @@ -17,7 +17,7 @@ * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). - * - Possibly register a custom database back-end area using ::OrthancPluginRegisterDatabaseBackend(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackend(). * -# void OrthancPluginFinalize(): * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -29,8 +29,11 @@ * The name and the version of a plugin is only used to prevent it * from being loaded twice. * - * The various REST callbacks are guaranteed to be executed in mutual - * exclusion since Orthanc 0.8.5. + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). **/ @@ -361,6 +364,7 @@ _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, _OrthancPluginService_RegisterStorageArea = 1002, _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -857,10 +861,13 @@ * expression for a URI. This function must be called during the * initialization of the plugin, i.e. inside the * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param pathRegularExpression Regular expression for the URI. May contain groups. * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() **/ ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( OrthancPluginContext* context, @@ -875,6 +882,39 @@ + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, it is up to the plugin to + * implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + typedef struct { OrthancPluginOnStoredInstanceCallback callback; @@ -2468,7 +2508,7 @@ * This function returns the description of a given error code. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param code The error code of interest. + * @param error The error code of interest. * @return The error description. This is a statically-allocated * string, do not free it. **/ @@ -2520,6 +2560,8 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param output The HTTP connection to the client application. * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. * @see OrthancPluginSendHttpStatusCode() **/ ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( diff -r 2bac60a4f584 -r e1e54a73ba8b Plugins/Include/orthanc/OrthancCppDatabasePlugin.h --- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Thu Aug 27 12:56:48 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Thu Aug 27 14:58:58 2015 +0200 @@ -41,6 +41,7 @@ namespace OrthancPlugins { +//! @cond Doxygen_Suppress // This class mimics "boost::noncopyable" class NonCopyable { @@ -58,6 +59,7 @@ { } }; +//! @endcond @@ -422,6 +424,13 @@ + /** + * @brief Bridge between C and C++ database engines. + * + * Class creating the bridge between the C low-level primitives for + * custom database engines, and the high-level IDatabaseBackend C++ + * interface. + **/ class DatabaseBackendAdapter { private: @@ -1467,6 +1476,13 @@ public: + /** + * Register a custom database back-end written in C++. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend Your custom database engine. + **/ + static void Register(OrthancPluginContext* context, IDatabaseBackend& backend) {