changeset 6635:2c137c796946 limited-memory

integration mainline->limited-memory
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 20 Mar 2026 17:43:50 +0100
parents a0bbb4d460b8 (diff) ab12547ac3df (current diff)
children
files
diffstat 22 files changed, 403 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Mar 20 17:05:49 2026 +0100
+++ b/NEWS	Fri Mar 20 17:43:50 2026 +0100
@@ -4,6 +4,8 @@
 General
 -------
 
+* Experimental WIP: try to limit the memory used by the HTTP server when multiple HTTP clients upload 
+  large DICOM files at the same time.  Right now, the limit is hardcoded to 8GB.  Check the TODO-MEM in the code.
 * New experimental configuration "PatientLevelEnabled" (TODO: work in progree)
 
 REST API
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Mar 20 17:43:50 2026 +0100
@@ -178,6 +178,7 @@
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/HttpServer/StringMatcher.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Logging.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MallocMemoryBuffer.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/../../Sources/MemoryManagedString.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/OrthancException.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/OrthancFramework.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/RestApi/RestApiHierarchy.cpp
--- a/OrthancFramework/Sources/Compression/ZipReader.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/Compression/ZipReader.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -375,6 +375,59 @@
     }
   }    
 
+  // TODO-MEM: find a way to share code between these 2 overloads
+  bool ZipReader::ReadNextFile(std::string& filename,
+                               MemoryManagedString& content)
+  {
+    assert(pimpl_->unzip_ != NULL);
+
+    if (pimpl_->done_)
+    {
+      return false;
+    }
+    else
+    {
+      unz_file_info64_s info;
+      if (unzGetCurrentFileInfo64(pimpl_->unzip_, &info, NULL, 0, NULL, 0, NULL, 0) != 0)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      filename.resize(info.size_filename);
+      if (!filename.empty() &&
+          unzGetCurrentFileInfo64(pimpl_->unzip_, &info, &filename[0],
+                                  static_cast<uLong>(filename.size()), NULL, 0, NULL, 0) != 0)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      content.resize(info.uncompressed_size);
+
+      if (!content.empty())
+      {
+        if (unzOpenCurrentFile(pimpl_->unzip_) == 0)
+        {
+          bool success = (unzReadCurrentFile(pimpl_->unzip_, &content[0],
+                                             static_cast<uLong>(content.size())) != 0);
+                          
+          if (unzCloseCurrentFile(pimpl_->unzip_) != 0 ||
+              !success)
+          {
+            throw OrthancException(ErrorCode_BadFileFormat);
+          }
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "Invalid file or unsupported compression method (e.g. Deflate64)");
+        }
+      }
+      
+      pimpl_->done_ = (unzGoToNextFile(pimpl_->unzip_) != 0);
+ 
+      return true;
+    }
+  }    
+
   
   ZipReader* ZipReader::CreateFromMemory(const void* buffer,
                                          size_t size)
--- a/OrthancFramework/Sources/Compression/ZipReader.h	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/Compression/ZipReader.h	Fri Mar 20 17:43:50 2026 +0100
@@ -43,6 +43,7 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
+#include "../MemoryManagedString.h"
 
 #if ORTHANC_SANDBOXED != 1
 #  include <boost/filesystem.hpp>
@@ -69,7 +70,10 @@
 
     bool ReadNextFile(std::string& filename,
                       std::string& content);
-    
+
+    bool ReadNextFile(std::string& filename,
+                      MemoryManagedString& content);
+                      
     static ZipReader* CreateFromMemory(const void* buffer,
                                        size_t size);
 
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -2603,6 +2603,11 @@
   DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
                                                        size_t size)
   {
+    if (!DicomMap::IsDicomFile(buffer, size))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "Not a DICOM file");
+    }
+
     DcmInputBufferStream is;
     if (size > 0)
     {
--- a/OrthancFramework/Sources/FileBuffer.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/FileBuffer.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -88,6 +88,17 @@
 
       file_.Read(target);
     }
+
+    void Read(MemoryManagedString& target)
+    {
+      if (isWriting_)
+      {
+        stream_.close();
+        isWriting_ = false;
+      }
+
+      file_.Read(target);
+    }
   };
 
     
@@ -110,4 +121,11 @@
     assert(pimpl_.get() != NULL);
     pimpl_->Read(target);
   }
+
+
+  void FileBuffer::Read(MemoryManagedString& target)
+  {
+    assert(pimpl_.get() != NULL);
+    pimpl_->Read(target);
+  }
 }
--- a/OrthancFramework/Sources/FileBuffer.h	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/FileBuffer.h	Fri Mar 20 17:43:50 2026 +0100
@@ -36,7 +36,7 @@
 
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
-
+#include "MemoryManagedString.h"
 
 namespace Orthanc
 {
@@ -53,5 +53,7 @@
                 size_t size);
 
     void Read(std::string& target);
+
+    void Read(MemoryManagedString& target);
   };
 }
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -30,6 +30,7 @@
 #include "../ChunkedBuffer.h"
 #include "../FileBuffer.h"
 #include "../Logging.h"
+#include "../MemoryManagedString.h"
 #include "../OrthancException.h"
 #include "../TemporaryFile.h"
 #include "../SystemToolbox.h"
@@ -480,7 +481,7 @@
                                             const std::string& username,
                                             const UriComponents& uri,
                                             const std::map<std::string, std::string>& headers,
-                                            const std::string& body,
+                                            const MemoryManagedString& body,
                                             const std::string& boundary,
                                             const std::string& authenticationPayload)
   {
@@ -488,12 +489,12 @@
           
     MultipartStreamReader reader(boundary);
     reader.SetHandler(handler);
-    reader.AddChunk(body);        
+    reader.AddChunk(body.c_str(), body.size());        
     reader.CloseStream();
   }
   
 
-  static PostDataStatus ReadBodyWithContentLength(std::string& body,
+  static PostDataStatus ReadBodyWithContentLength(MemoryManagedString& body,
                                                   struct mg_connection *connection,
                                                   const std::string& contentLength)
   {
@@ -533,7 +534,7 @@
   }
                                                   
 
-  static PostDataStatus ReadBodyWithoutContentLength(std::string& body,
+  static PostDataStatus ReadBodyWithoutContentLength(MemoryManagedString& body,
                                                      struct mg_connection *connection)
   {
     // Store the individual chunks in a temporary file, then read it
@@ -565,7 +566,7 @@
   }
                                                   
 
-  static PostDataStatus ReadBodyToString(std::string& body,
+  static PostDataStatus ReadBodyToString(MemoryManagedString& body,
                                          struct mg_connection *connection,
                                          const HttpToolbox::Arguments& headers)
   {
@@ -593,7 +594,7 @@
     if (contentLength != headers.end())
     {
       // "Content-Length" is available
-      std::string body;
+      MemoryManagedString body;
       PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second);
 
       if (status == PostDataStatus_Success &&
@@ -1039,10 +1040,11 @@
           else if (method == "PUT")
           {
 #if CIVETWEB_HAS_WEBDAV_WRITING == 1           
-            std::string body;
+            MemoryManagedString body;
             if (ReadBodyToString(body, connection, headers) == PostDataStatus_Success)
             {
-              if (bucket->second->StoreFile(body, path))
+              std::string bodyStr(body.begin(), body.end()); // TODO-MEM: avoid this copy and use a MemoryManagedString in StoreFile too 
+              if (bucket->second->StoreFile(bodyStr, path))
               {
                 //output.SendStatus(HttpStatus_200_Ok);
                 output.SendStatus(HttpStatus_201_Created);
@@ -1425,8 +1427,8 @@
 
     // Extract the body of the request for PUT and POST, or process
     // the body as a stream
+    MemoryManagedString body;
 
-    std::string body;
     if (method == HttpMethod_Post ||
         method == HttpMethod_Put)
     {
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.h	Fri Mar 20 17:43:50 2026 +0100
@@ -51,6 +51,7 @@
 
 #include "IIncomingHttpRequestFilter.h"
 #include "../MetricsRegistry.h"
+#include "../MemoryManagedString.h"
 
 #include <list>
 #include <map>
@@ -234,7 +235,7 @@
                                   const std::string& username,
                                   const UriComponents& uri,
                                   const std::map<std::string, std::string>& headers,
-                                  const std::string& body,
+                                  const MemoryManagedString& body,
                                   const std::string& boundary,
                                   const std::string& authenticationPayload);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MemoryManagedString.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MemoryManagedString.h"
+
+#include "OrthancException.h"
+#include "MultiThreading/Semaphore.h"
+#include <boost/lexical_cast.hpp>
+#include "Logging.h"
+
+
+namespace Orthanc
+{
+  static std::unique_ptr<Semaphore> availableMemorySemaphore_;
+  static size_t maximumMemorySize_ = 0;
+
+  void LimitedMemoryAllocator::Initialize(size_t maximumMemorySize)
+  {
+    if (availableMemorySemaphore_.get() != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    
+    maximumMemorySize_ = maximumMemorySize;
+    availableMemorySemaphore_.reset(new Semaphore(maximumMemorySize));
+  }
+
+  void* LimitedMemoryAllocator::Allocate(size_t elemSize, size_t numElem)
+  {
+    if (availableMemorySemaphore_.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    size_t requestedSize = numElem * elemSize;
+
+    if (requestedSize > maximumMemorySize_)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory, "Trying to allocate a buffer ( " + boost::lexical_cast<std::string>(requestedSize) + " bytes) that is larger than the maximum memory size ( " + boost::lexical_cast<std::string>(maximumMemorySize_) + " bytes)");
+    }
+
+    // wait until there is enough memory available
+    availableMemorySemaphore_->Acquire(requestedSize);
+
+    // LOG(TRACE) << "Reserved " << requestedSize << " bytes in memory.  Remaining size: " << availableMemorySemaphore_->GetAvailableResourcesCount() << " bytes";
+
+    void* p = malloc(numElem * elemSize);
+    if (!p) 
+    {
+      throw std::bad_alloc();  // the allocation might still fail since we don't track the whole memory
+    }
+    return p;
+  }
+
+  void LimitedMemoryAllocator::Deallocate(void* p, size_t elemSize, size_t numElem)
+  {
+    free(p);
+    availableMemorySemaphore_->Release(elemSize * numElem);
+
+    // LOG(TRACE) << "Released " << (elemSize * numElem) << " bytes in memory.  Remaining size: " << availableMemorySemaphore_->GetAvailableResourcesCount() << " bytes";
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/MemoryManagedString.h	Fri Mar 20 17:43:50 2026 +0100
@@ -0,0 +1,127 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <memory>
+#include <cstdlib>
+#include <string>
+
+namespace Orthanc
+{
+  template<typename T> class LimitedStdAllocator;
+
+  // TODO-MEM: move this to a dedicated file
+  class LimitedMemoryAllocator
+  {
+  public:
+    static void Initialize(size_t maximumMemorySize);
+  protected:
+    static void* Allocate(size_t elemSize, size_t numElem);
+
+    static void Deallocate(void* p, size_t elemSize, size_t numElem);
+    
+    template<typename T>friend class LimitedStdAllocator;
+  };
+
+
+  template<typename T>
+  class LimitedStdAllocator 
+  {
+  public:
+    typedef T value_type;
+    typedef T* pointer;
+    typedef const T* const_pointer;
+    typedef T& reference;
+    typedef const T& const_reference;
+    typedef std::size_t size_type;
+    typedef std::ptrdiff_t difference_type;
+
+    // Rebind to allow allocator for other types
+    template<typename U>
+    struct rebind 
+    {
+      typedef LimitedStdAllocator<U> other;
+    };
+
+    LimitedStdAllocator() 
+    {
+    }
+
+    template<typename U>
+    LimitedStdAllocator(const LimitedStdAllocator<U>&) 
+    {
+    }
+
+    pointer allocate(size_type num, const void* hint = 0)
+    {
+      return static_cast<pointer>(LimitedMemoryAllocator::Allocate(sizeof(T), num));
+    }
+
+    void deallocate(pointer p, size_type num)
+    {
+      LimitedMemoryAllocator::Deallocate(p, sizeof(T), num);
+    }
+
+    void construct(pointer p, const T& val) 
+    {
+      new (p) T(val);
+    }
+
+    void destroy(pointer p) 
+    {
+      p->~T();
+    }
+
+    pointer address(reference x) const 
+    {
+      return &x;
+    }
+
+    const_pointer address(const_reference x) const 
+    {
+      return &x;
+    }
+
+    size_type max_size() const 
+    {
+      return size_type(-1) / sizeof(T);
+    }
+  };
+
+
+  // // Equality check
+  // template<typename T1, typename T2>
+  // bool operator==(const LimitedStdAllocator<T1>&, const LimitedStdAllocator<T2>&)
+  // {
+  //     return true;
+  // }
+
+  // template<typename T1, typename T2>
+  // bool operator!=(const LimitedStdAllocator<T1>&, const LimitedStdAllocator<T2>&) throw() {
+  //     return false;
+  // }
+
+  typedef std::basic_string<char, std::char_traits<char>, LimitedStdAllocator<char> > MemoryManagedString;
+}
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -266,6 +266,52 @@
     }
   }
 
+  // TODO-MEM: find a way to share code between these 2 overloads
+  void SystemToolbox::ReadFile(MemoryManagedString& content,
+                               const boost::filesystem::path& path,
+                               bool log)
+  {
+    if (!IsRegularFile(path))
+    {
+      throw OrthancException(ErrorCode_RegularFileExpected,
+                             "The path does not point to a regular file: " + PathToUtf8(path), log);
+    }
+
+    try
+    {
+      boost::filesystem::ifstream f;
+      f.open(path, std::ifstream::in | std::ifstream::binary);
+      if (!f.good())
+      {
+        throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path), log);
+      }
+
+      std::streamsize size = GetStreamSize(f);
+      content.resize(static_cast<size_t>(size));
+
+      if (static_cast<std::streamsize>(content.size()) != size)
+      {
+        throw OrthancException(ErrorCode_InternalError,
+                               "Reading a file that is too large for a 32bit architecture");
+      }
+    
+      if (size != 0)
+      {
+        f.read(&content[0], size);
+      }
+
+      f.close();
+    }
+    catch (boost::filesystem::filesystem_error&)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+    catch (...)  // To catch "std::system_error&" in C++11
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+  }
+
 
   bool SystemToolbox::ReadHeader(std::string& header,
                                  const boost::filesystem::path& path,
--- a/OrthancFramework/Sources/SystemToolbox.h	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/SystemToolbox.h	Fri Mar 20 17:43:50 2026 +0100
@@ -45,6 +45,7 @@
 #include <string>
 #include <stdint.h>
 #include <boost/filesystem.hpp>
+#include "MemoryManagedString.h"
 
 // Note: The use of "boost::filesystem::path" is mandatory to handle
 // non ASCII-only path on Windows
@@ -70,6 +71,16 @@
       ReadFile(content, path, true /* log */);
     }
 
+    static void ReadFile(MemoryManagedString& content,
+                         const boost::filesystem::path& path, 
+                         bool log);
+
+    static inline void ReadFile(MemoryManagedString& content,
+                                const boost::filesystem::path& path)
+    {
+      ReadFile(content, path, true /* log */);
+    }
+
     static bool ReadHeader(std::string& header,
                            const boost::filesystem::path& path,
                            size_t headerSize);
--- a/OrthancFramework/Sources/TemporaryFile.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/TemporaryFile.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -129,6 +129,21 @@
   }
 
 
+  void TemporaryFile::Read(MemoryManagedString& content) const
+  {
+    try
+    {
+      SystemToolbox::ReadFile(content, path_);
+    }
+    catch (OrthancException& e)
+    {
+      throw OrthancException(e.GetErrorCode(), 
+                             "Can't read temporary file \"" + SystemToolbox::PathToUtf8(path_) +
+                             "\": Another process has corrupted the temporary directory");
+    }
+  }
+  
+
   void TemporaryFile::Touch()
   {
     std::string empty;
--- a/OrthancFramework/Sources/TemporaryFile.h	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancFramework/Sources/TemporaryFile.h	Fri Mar 20 17:43:50 2026 +0100
@@ -38,6 +38,7 @@
 #include <boost/filesystem.hpp>
 #include <stdint.h>
 #include <string>
+#include "MemoryManagedString.h"
 
 namespace Orthanc
 {
@@ -60,6 +61,8 @@
 
     void Read(std::string& content) const;
 
+    void Read(MemoryManagedString& content) const;
+
     void Touch();
 
     uint64_t GetFileSize() const;
--- a/OrthancServer/Plugins/Samples/ConnectivityChecks/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Plugins/Samples/ConnectivityChecks/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -50,5 +50,7 @@
 #include "../../../../OrthancFramework/Sources/Enumerations.cpp"
 #include "../../../../OrthancFramework/Sources/Logging.cpp"
 #include "../../../../OrthancFramework/Sources/OrthancException.cpp"
+#include "../../../../OrthancFramework/Sources/MemoryManagedString.cpp"
+#include "../../../../OrthancFramework/Sources/MultiThreading/Semaphore.cpp"
 #include "../../../../OrthancFramework/Sources/SystemToolbox.cpp"
 #include "../../../../OrthancFramework/Sources/Toolbox.cpp"
--- a/OrthancServer/Plugins/Samples/DelayedDeletion/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Plugins/Samples/DelayedDeletion/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -53,7 +53,9 @@
 #include "../../../../OrthancFramework/Sources/Enumerations.cpp"
 #include "../../../../OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp"
 #include "../../../../OrthancFramework/Sources/Logging.cpp"
+#include "../../../../OrthancFramework/Sources/MemoryManagedString.cpp"
 #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp"
+#include "../../../../OrthancFramework/Sources/MultiThreading/Semaphore.cpp"
 #include "../../../../OrthancFramework/Sources/OrthancException.cpp"
 #include "../../../../OrthancFramework/Sources/SQLite/Connection.cpp"
 #include "../../../../OrthancFramework/Sources/SQLite/FunctionContext.cpp"
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -78,9 +78,11 @@
 #include "../../../../OrthancFramework/Sources/Images/PngReader.cpp"
 #include "../../../../OrthancFramework/Sources/Images/PngWriter.cpp"
 #include "../../../../OrthancFramework/Sources/Logging.cpp"
+#include "../../../../OrthancFramework/Sources/MemoryManagedString.cpp"
 #include "../../../../OrthancFramework/Sources/MetricsRegistry.cpp"
 #include "../../../../OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp"
 #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp"
+#include "../../../../OrthancFramework/Sources/MultiThreading/Semaphore.cpp"
 #include "../../../../OrthancFramework/Sources/OrthancException.cpp"
 #include "../../../../OrthancFramework/Sources/OrthancFramework.cpp"
 #include "../../../../OrthancFramework/Sources/RestApi/RestApiOutput.cpp"
--- a/OrthancServer/Plugins/Samples/ServeFolders/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Plugins/Samples/ServeFolders/OrthancFrameworkDependencies.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -50,5 +50,7 @@
 #include "../../../../OrthancFramework/Sources/Enumerations.cpp"
 #include "../../../../OrthancFramework/Sources/Logging.cpp"
 #include "../../../../OrthancFramework/Sources/OrthancException.cpp"
+#include "../../../../OrthancFramework/Sources/MemoryManagedString.cpp"
+#include "../../../../OrthancFramework/Sources/MultiThreading/Semaphore.cpp"
 #include "../../../../OrthancFramework/Sources/SystemToolbox.cpp"
 #include "../../../../OrthancFramework/Sources/Toolbox.cpp"
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -177,14 +177,15 @@
 
       Json::Value answer = Json::arrayValue;
       
-      std::string filename, content;
+      std::string filename;
+      MemoryManagedString content;
       while (reader->ReadNextFile(filename, content))
       {
         if (!content.empty())
         {
           LOG(INFO) << "Uploading DICOM file from ZIP archive: " << filename;
 
-          std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromBuffer(content));
+          std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromBuffer(content.c_str(), content.size()));
           toStore->SetOrigin(DicomInstanceOrigin::FromRest(call));
 
           try
--- a/OrthancServer/Sources/OrthancWebDav.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Sources/OrthancWebDav.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -1213,7 +1213,7 @@
         std::unique_ptr<ZipReader> reader(ZipReader::CreateFromMemory(content));
 
         std::string filename, uncompressedFile;
-        while (reader->ReadNextFile(filename, uncompressedFile))
+        while (reader->ReadNextFile(filename, uncompressedFile))  // TODO-MEM: use MemoryManagedString
         {
           if (!uncompressedFile.empty())
           {
--- a/OrthancServer/Sources/main.cpp	Fri Mar 20 17:05:49 2026 +0100
+++ b/OrthancServer/Sources/main.cpp	Fri Mar 20 17:43:50 2026 +0100
@@ -1959,6 +1959,10 @@
   Logging::SetCurrentThreadName("MAIN");
   SetGlobalVerbosity(Verbosity_Default);
 
+  uint64_t maxMemorySize = 8ul * 1024ul * 1024ul * 1024ul;  // TODO-MEM: get this value from the configuration file
+  maxMemorySize = std::min(maxMemorySize, std::numeric_limits<size_t>::max()); // on 32 bits system, limit the value to 4GB
+  LimitedMemoryAllocator::Initialize(static_cast<size_t>(maxMemorySize));
+
   bool upgradeDatabase = false;
   bool loadJobsFromDatabase = true;
   boost::filesystem::path configurationFile;