changeset 4366:6a39ca7083b9

New config option "MallocArenaMax" to control memory usage on GNU/Linux
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Dec 2020 09:32:39 +0100
parents 3150306fb4ad
children 189e48f4a92a
files NEWS OrthancServer/CMakeLists.txt OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancInitialization.cpp
diffstat 4 files changed, 63 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Dec 09 16:46:10 2020 +0100
+++ b/NEWS	Thu Dec 10 09:32:39 2020 +0100
@@ -5,6 +5,7 @@
 -------
 
 * ZIP archives containing DICOM files can be uploaded using WebDAV
+* New config option "MallocArenaMax" to control memory usage on GNU/Linux
 
 REST API
 --------
--- a/OrthancServer/CMakeLists.txt	Wed Dec 09 16:46:10 2020 +0100
+++ b/OrthancServer/CMakeLists.txt	Thu Dec 10 09:32:39 2020 +0100
@@ -284,6 +284,15 @@
 ## Configuration of the C/C++ macros
 #####################################################################
 
+check_symbol_exists(mallopt "malloc.h" HAVE_MALLOPT)
+
+if (HAVE_MALLOPT)
+  add_definitions(-DHAVE_MALLOPT=1)
+else()
+  add_definitions(-DHAVE_MALLOPT=0)
+endif()
+
+
 if (STATIC_BUILD)
   add_definitions(-DORTHANC_STATIC=1)
 else()
--- a/OrthancServer/Resources/Configuration.json	Wed Dec 09 16:46:10 2020 +0100
+++ b/OrthancServer/Resources/Configuration.json	Thu Dec 10 09:32:39 2020 +0100
@@ -618,5 +618,16 @@
   // immediately written to the disk. This option only makes sense if
   // the builtin filesystem storage area is used. It defaults to
   // "false" in Orthanc <= 1.7.3, and to "true" in Orthanc >= 1.7.4.
-  "SyncStorageArea" : true
+  "SyncStorageArea" : true,
+
+  // If specified, on compatible systems, call "mallopt(M_ARENA_MAX,
+  // ...)" while starting Orthanc. This has the same effect at setting
+  // the environment variable "MALLOC_ARENA_MAX". This avoids large
+  // growth in RES memory if the threads of the embedded HTTP server
+  // have to allocate large chunks of memory (typically the case with
+  // large DICOM files). By setting "MallocArenaMax" to "N", these
+  // threads share "N" memory pools (known as "arenas"). Setting this
+  // option to "0" doesn't call mallopt()", which was the behavior of
+  // Orthanc <= 1.8.1.
+  "MallocArenaMax" : 5 
 }
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Wed Dec 09 16:46:10 2020 +0100
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Thu Dec 10 09:32:39 2020 +0100
@@ -38,6 +38,14 @@
 #  include <winsock2.h>
 #endif
 
+#if !defined(HAVE_MALLOPT)
+#  error Macro HAVE_MALLOPT must be defined
+#endif
+
+#if HAVE_MALLOPT == 1
+#  include <malloc.h>
+#endif
+
 #include "OrthancInitialization.h"
 
 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
@@ -227,6 +235,11 @@
 
   void OrthancInitialize(const char* configurationFile)
   {
+    static const char* LOCALE = "Locale";
+    static const char* PKCS11 = "Pkcs11";
+    static const char* DEFAULT_ENCODING = "DefaultEncoding";
+    static const char* MALLOC_ARENA_MAX = "MallocArenaMax";
+    
     OrthancConfiguration::WriterLock lock;
 
     InitializeServerEnumerations();
@@ -237,9 +250,9 @@
     {
       std::string locale;
       
-      if (lock.GetJson().isMember("Locale"))
+      if (lock.GetJson().isMember(LOCALE))
       {
-        locale = lock.GetConfiguration().GetStringParameter("Locale", "");
+        locale = lock.GetConfiguration().GetStringParameter(LOCALE, "");
       }
       
       bool loadPrivate = lock.GetConfiguration().GetBooleanParameter("LoadPrivateDictionary", true);
@@ -248,9 +261,9 @@
 
     // The Orthanc framework is now initialized
 
-    if (lock.GetJson().isMember("DefaultEncoding"))
+    if (lock.GetJson().isMember(DEFAULT_ENCODING))
     {
-      std::string encoding = lock.GetConfiguration().GetStringParameter("DefaultEncoding", "");
+      std::string encoding = lock.GetConfiguration().GetStringParameter(DEFAULT_ENCODING, "");
       SetDefaultDicomEncoding(StringToEncoding(encoding.c_str()));
     }
     else
@@ -258,9 +271,9 @@
       SetDefaultDicomEncoding(ORTHANC_DEFAULT_DICOM_ENCODING);
     }
 
-    if (lock.GetJson().isMember("Pkcs11"))
+    if (lock.GetJson().isMember(PKCS11))
     {
-      ConfigurePkcs11(lock.GetJson()["Pkcs11"]);
+      ConfigurePkcs11(lock.GetJson()[PKCS11]);
     }
 
     RegisterUserMetadata(lock.GetJson());
@@ -269,6 +282,28 @@
     LoadCustomDictionary(lock.GetJson());
 
     lock.GetConfiguration().RegisterFont(ServerResources::FONT_UBUNTU_MONO_BOLD_16);
+
+#if HAVE_MALLOPT == 1
+    // New in Orthanc 1.9.0
+    // https://book.orthanc-server.com/faq/scalability.html#controlling-memory-usage
+    unsigned int maxArena = lock.GetConfiguration().GetUnsignedIntegerParameter(MALLOC_ARENA_MAX, 5);
+    if (maxArena != 0)
+    {
+      // https://man7.org/linux/man-pages/man3/mallopt.3.html
+      LOG(INFO) << "Calling mallopt(M_ARENA_MAX, " << maxArena << ")";
+      if (mallopt(M_ARENA_MAX, maxArena) != 1 /* success */)
+      {
+        throw OrthancException(ErrorCode_InternalError, "The call to mallopt(M_ARENA_MAX, " +
+                               boost::lexical_cast<std::string>(maxArena) + ") has failed");
+      }
+    }
+#else
+    if (lock.GetJson().isMember(MALLOC_ARENA_MAX))
+    {
+      LOG(INFO) << "Your platform does not support mallopt(), ignoring configuration option \""
+                << MALLOC_ARENA_MAX << "\"";
+    }
+#endif
   }