changeset 1595:e1e54a73ba8b

OrthancPluginRegisterRestCallbackNoLock, documentation
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 27 Aug 2015 14:58:58 +0200
parents 2bac60a4f584
children f2e3d030ea59
files NEWS Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCDatabasePlugin.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h
diffstat 6 files changed, 154 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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<std::string, _OrthancPluginProperty>  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<OrthancPluginRestOutput*>(&output), 
+                         flatUri.c_str(), 
+                         &request);
+      }
 
-    typedef std::pair<boost::regex*, OrthancPluginRestCallback> RestCallback;
-    typedef std::list<RestCallback>  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<std::string, _OrthancPluginProperty>  Property;
+    typedef std::list<RestCallback*>  RestCallbacks;
     typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
     typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
     typedef std::map<Property, std::string>  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<std::string> groups;
     std::vector<const char*> 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<OrthancPluginRestOutput*>(&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<const _OrthancPluginRestCallback*>(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:
--- 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);
 
--- 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
--- 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().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    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, &params);
+  }
+
+
+
   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(
--- 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)
     {