changeset 5954:4f726a09798e

MaximumConcurrentDcmtkTranscoders
author Alain Mazy <am@orthanc.team>
date Wed, 15 Jan 2025 09:28:27 +0100
parents 684d49e47b5e
children 34ba0344f240
files NEWS OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Resources/Configuration.json OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/main.cpp OrthancServer/UnitTestsSources/ServerIndexTests.cpp OrthancServer/UnitTestsSources/ServerJobsTests.cpp
diffstat 10 files changed, 51 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Jan 10 18:27:27 2025 +0100
+++ b/NEWS	Wed Jan 15 09:28:27 2025 +0100
@@ -1,6 +1,15 @@
 Pending changes in the mainline
 ===============================
 
+General
+-------
+
+* New configuration options:
+  - "MaximumConcurrentDcmtkTranscoders" to reduce CPU and memory usage by limiting
+    the number of concurrent DCMTK transcoders that are simultaneously running
+    at any given time.
+
+
 Maintenance
 -----------
 
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -50,8 +50,9 @@
 
 namespace Orthanc
 {
-  DcmtkTranscoder::DcmtkTranscoder() :
-    lossyQuality_(90)
+  DcmtkTranscoder::DcmtkTranscoder(unsigned int maxConcurrentExecutions) :
+    lossyQuality_(90),
+    maxConcurrentExecutionsSemaphore_(maxConcurrentExecutions)
   {
   }
 
@@ -317,6 +318,8 @@
                                   const std::set<DicomTransferSyntax>& allowedSyntaxes,
                                   bool allowNewSopInstanceUid)
   {
+    Semaphore::Locker lock(maxConcurrentExecutionsSemaphore_); // limit the number of concurrent executions
+
     target.Clear();
     
     DicomTransferSyntax sourceSyntax;
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h	Wed Jan 15 09:28:27 2025 +0100
@@ -33,6 +33,8 @@
 #endif
 
 #include "IDicomTranscoder.h"
+#include "../MultiThreading/Semaphore.h"
+
 
 namespace Orthanc
 {
@@ -40,7 +42,8 @@
   {
   private:
     unsigned int  lossyQuality_;
-    
+    Semaphore maxConcurrentExecutionsSemaphore_;
+
     bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */,
                           std::string& failureReason /* out */,
                           DcmFileFormat& dicom,
@@ -48,7 +51,7 @@
                           bool allowNewSopInstanceUid);
     
   public:
-    DcmtkTranscoder();
+    DcmtkTranscoder(unsigned int maxConcurrentExecutions);
 
     void SetLossyQuality(unsigned int quality);
 
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -3561,7 +3561,7 @@
   scu.SetCommonClassesProposed(false);
   scu.SetRetiredBigEndianProposed(true);
 
-  DcmtkTranscoder transcoder;
+  DcmtkTranscoder transcoder(1);
 
   for (int j = 0; j < 2; j++)
   {
@@ -3618,7 +3618,7 @@
   DicomTransferSyntax sourceSyntax;
   ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
 
-  DcmtkTranscoder transcoder;
+  DcmtkTranscoder transcoder(1);
 
   for (int i = 0; i <= DicomTransferSyntax_XML; i++)
   {
--- a/OrthancServer/Resources/Configuration.json	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/Resources/Configuration.json	Wed Jan 15 09:28:27 2025 +0100
@@ -1025,7 +1025,12 @@
   // In this mode, many Orthanc features that requires a write access to the 
   // Index DB or the disk storage won't be available at all.
   // (new in Orthanc 1.12.5)
-  "ReadOnly" : false
+  "ReadOnly" : false,
 
+  // Maximum number of DCMTK transcoders that are simultaneously running
+  // at any given time. A value of "0" indicates to use all the
+  // available CPU logical cores. Prior to Orthanc 1.12.6, there were not limit.
+  // (new in Orthanc 1.12.6)
+  "MaximumConcurrentDcmtkTranscoders" : 0
 
 }
--- a/OrthancServer/Sources/ServerContext.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -356,7 +356,8 @@
                                IStorageArea& area,
                                bool unitTesting,
                                size_t maxCompletedJobs,
-                               bool readOnly) :
+                               bool readOnly,
+                               unsigned int maxConcurrentDcmtkTranscoder) :
     index_(*this, database, (unitTesting ? 20 : 500), readOnly),
     area_(area),
     compressionEnabled_(false),
@@ -378,7 +379,7 @@
     isExecuteLuaEnabled_(false),
     isRestApiWriteToFileSystemEnabled_(false),
     overwriteInstances_(false),
-    dcmtkTranscoder_(new DcmtkTranscoder),
+    dcmtkTranscoder_(new DcmtkTranscoder(maxConcurrentDcmtkTranscoder)),
     isIngestTranscoding_(false),
     ingestTranscodingOfUncompressed_(true),
     ingestTranscodingOfCompressed_(true),
--- a/OrthancServer/Sources/ServerContext.h	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/Sources/ServerContext.h	Wed Jan 15 09:28:27 2025 +0100
@@ -306,7 +306,8 @@
                   IStorageArea& area,
                   bool unitTesting,
                   size_t maxCompletedJobs,
-                  bool readOnly);
+                  bool readOnly,
+                  unsigned int maxConcurrentDcmtkTranscoder);
 
     ~ServerContext();
 
--- a/OrthancServer/Sources/main.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/Sources/main.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -64,6 +64,8 @@
 static const char* const KEY_DICOM_TLS_ACCEPTED_CIPHERS = "DicomTlsCiphersAccepted";
 static const char* const KEY_MAXIMUM_PDU_LENGTH = "MaximumPduLength";
 static const char* const KEY_READ_ONLY = "ReadOnly";
+static const char* const KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS = "MaximumConcurrentDcmtkTranscoders";
+
 
 class OrthancStoreRequestHandler : public IStoreRequestHandler
 {
@@ -1519,7 +1521,8 @@
 {
   size_t maxCompletedJobs;
   bool readOnly;
-  
+  unsigned int maxDcmtkConcurrentTranscoders;
+
   {
     OrthancConfiguration::ReaderLock lock;
 
@@ -1555,6 +1558,13 @@
     // New option in Orthanc 1.12.5
     readOnly = lock.GetConfiguration().GetBooleanParameter(KEY_READ_ONLY, false);
     
+    // New option in Orthanc 1.12.6
+    maxDcmtkConcurrentTranscoders = lock.GetConfiguration().GetUnsignedIntegerParameter(KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS, 0);
+    if (maxDcmtkConcurrentTranscoders == 0)
+    {
+      maxDcmtkConcurrentTranscoders = static_cast<unsigned int>(boost::thread::hardware_concurrency());
+    }
+
     // Configuration of DICOM TLS for Orthanc SCU (since Orthanc 1.9.0)
     DicomAssociationParameters::SetDefaultOwnCertificatePath(
       lock.GetConfiguration().GetStringParameter(KEY_DICOM_TLS_PRIVATE_KEY, ""),
@@ -1569,7 +1579,7 @@
       lock.GetConfiguration().GetBooleanParameter(KEY_DICOM_TLS_REMOTE_CERTIFICATE_REQUIRED, true));
   }
   
-  ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs, readOnly);
+  ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs, readOnly, maxDcmtkConcurrentTranscoders);
 
   {
     OrthancConfiguration::ReaderLock lock;
@@ -1974,7 +1984,7 @@
           SQLiteDatabaseWrapper inMemoryDatabase;
           inMemoryDatabase.Open();
           MemoryStorageArea inMemoryStorage;
-          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */);
+          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
           OrthancRestApi restApi(context, false /* no Orthanc Explorer */);
           restApi.GenerateOpenApiDocumentation(openapi);
           context.Stop();
@@ -2025,7 +2035,7 @@
           SQLiteDatabaseWrapper inMemoryDatabase;
           inMemoryDatabase.Open();
           MemoryStorageArea inMemoryStorage;
-          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */);
+          ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
           OrthancRestApi restApi(context, false /* no Orthanc Explorer */);
           restApi.GenerateReStructuredTextCheatSheet(cheatsheet, "https://orthanc.uclouvain.be/api/index.html");
           context.Stop();
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -621,7 +621,7 @@
   FilesystemStorage storage(path);
   SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
   db.Open();
-  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
   context.SetupJobsEngine(true, false);
 
   ServerIndex& index = context.GetIndex();
@@ -703,7 +703,7 @@
   FilesystemStorage storage(path);
   SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
   db.Open();
-  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+  ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
   context.SetupJobsEngine(true, false);
   ServerIndex& index = context.GetIndex();
 
@@ -820,7 +820,7 @@
     MemoryStorageArea storage;
     SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
     db.Open();
-    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
     context.SetupJobsEngine(true, false);
     context.SetCompressionEnabled(true);
 
@@ -985,7 +985,7 @@
     MemoryStorageArea storage;
     SQLiteDatabaseWrapper db;   // The SQLite DB is in memory
     db.Open();
-    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */);
+    ServerContext context(db, storage, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */);
     context.SetupJobsEngine(true, false);
     context.SetCompressionEnabled(compression);
 
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Fri Jan 10 18:27:27 2025 +0100
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Wed Jan 15 09:28:27 2025 +0100
@@ -536,7 +536,7 @@
     OrthancJobsSerialization()
     {
       db_.Open();
-      context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10, false /* readonly */));
+      context_.reset(new ServerContext(db_, storage_, true /* running unit tests */, 10, false /* readonly */, 1 /* DCMTK concurrent transcoders */));
       context_->SetupJobsEngine(true, false);
     }