changeset 3857:67f988f42cef transcoding

integration mainline->transcoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 21 Apr 2020 16:33:59 +0200
parents 44bfcfdf42e8 (current diff) dd0fcbf6a791 (diff)
children eb8280b30031
files Core/DicomNetworking/IDicomConnectionManager.h OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp OrthancServer/main.cpp UnitTestsSources/MultiThreadingTests.cpp
diffstat 33 files changed, 195 insertions(+), 290 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/IDicomConnectionManager.h	Mon Apr 20 14:04:14 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if !defined(ORTHANC_ENABLE_DCMTK_NETWORKING)
-#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0
-
-namespace Orthanc
-{
-  // DICOM networking is disabled, this is just a void class
-  class IDicomConnectionManager : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomConnectionManager()
-    {
-    }
-  };
-}
-
-#else
-
-#include "DicomUserConnection.h"
-
-namespace Orthanc
-{
-  class IDicomConnectionManager : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomConnectionManager()
-    {
-    }
-
-    class IResource : public boost::noncopyable
-    {
-    public:
-      virtual ~IResource()
-      {
-      }
-
-      virtual DicomUserConnection& GetConnection() = 0;
-    };
-
-    virtual IResource* AcquireConnection(const std::string& localAet,
-                                         const RemoteModalityParameters& remote) = 0;
-  };
-}
-
-#endif
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -44,64 +44,59 @@
     return boost::posix_time::microsec_clock::universal_time();
   }
 
-  class TimeoutDicomConnectionManager::Resource : public IDicomConnectionManager::IResource
+
+  TimeoutDicomConnectionManager::Lock::Lock(TimeoutDicomConnectionManager& that,
+                                            const std::string& localAet,
+                                            const RemoteModalityParameters& remote) : 
+    that_(that),
+    lock_(that_.mutex_)
   {
-  private:
-    TimeoutDicomConnectionManager&  that_;
+    // Calling "Touch()" will be done by the "~Lock()" destructor
+    that_.OpenInternal(localAet, remote);
+  }
 
-  public:
-    Resource(TimeoutDicomConnectionManager& that) : 
-      that_(that)
+  
+  TimeoutDicomConnectionManager::Lock::~Lock()
+  {
+    that_.TouchInternal();
+  }
+
+  
+  DicomUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection()
+  {
+    if (that_.connection_.get() == NULL)
     {
-      if (that_.connection_.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
+      // The allocation should have been done by "that_.Open()" in the constructor
+      throw OrthancException(ErrorCode_InternalError);
     }
-
-    ~Resource()
+    else
     {
-      that_.Touch();
-    }
-
-    DicomUserConnection& GetConnection()
-    {
-      assert(that_.connection_.get() != NULL);
       return *that_.connection_;
     }
-  };
+  }
 
 
-  void TimeoutDicomConnectionManager::Touch()
+  // Mutex must be locked
+  void TimeoutDicomConnectionManager::TouchInternal()
   {
     lastUse_ = GetNow();
   }
 
 
-  void TimeoutDicomConnectionManager::CheckTimeoutInternal()
+  // Mutex must be locked
+  void TimeoutDicomConnectionManager::OpenInternal(const std::string& localAet,
+                                                   const RemoteModalityParameters& remote)
   {
-    if (connection_.get() != NULL &&
-        (GetNow() - lastUse_) >= timeout_)
+    if (connection_.get() == NULL ||
+        !connection_->IsSameAssociation(localAet, remote))
     {
-      Close();
+      connection_.reset(new DicomUserConnection(localAet, remote));
     }
   }
 
 
-  void TimeoutDicomConnectionManager::SetTimeout(unsigned int timeout)
-  {
-    timeout_ = boost::posix_time::milliseconds(timeout);
-    CheckTimeoutInternal();
-  }
-
-
-  unsigned int TimeoutDicomConnectionManager::GetTimeout()
-  {
-    return static_cast<unsigned int>(timeout_.total_milliseconds());
-  }
-
-
-  void TimeoutDicomConnectionManager::Close()
+  // Mutex must be locked
+  void TimeoutDicomConnectionManager::CloseInternal()
   {
     if (connection_.get() != NULL)
     {
@@ -113,22 +108,29 @@
   }
 
 
-  void TimeoutDicomConnectionManager::CheckTimeout()
+  void TimeoutDicomConnectionManager::SetInactivityTimeout(unsigned int milliseconds)
   {
-    CheckTimeoutInternal();
+    boost::mutex::scoped_lock lock(mutex_);
+    timeout_ = boost::posix_time::milliseconds(milliseconds);
+    CloseInternal();
   }
 
 
-  IDicomConnectionManager::IResource* 
-  TimeoutDicomConnectionManager::AcquireConnection(const std::string& localAet,
-                                                   const RemoteModalityParameters& remote)
+  unsigned int TimeoutDicomConnectionManager::GetInactivityTimeout()
   {
-    if (connection_.get() == NULL ||
-        !connection_->IsSameAssociation(localAet, remote))
+    boost::mutex::scoped_lock lock(mutex_);
+    return static_cast<unsigned int>(timeout_.total_milliseconds());
+  }
+
+
+  void TimeoutDicomConnectionManager::CloseIfInactive()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (connection_.get() != NULL &&
+        (GetNow() - lastUse_) >= timeout_)
     {
-      connection_.reset(new DicomUserConnection(localAet, remote));
+      CloseInternal();
     }
-
-    return new Resource(*this);
   }
 }
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Tue Apr 21 16:33:59 2020 +0200
@@ -33,72 +33,72 @@
 
 #pragma once
 
-#include "IDicomConnectionManager.h"
+#if !defined(ORTHANC_ENABLE_DCMTK_NETWORKING)
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined
+#endif
 
-#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be 1 to use this file
+#endif
+
+
+#include "../Compatibility.h"
+#include "DicomUserConnection.h"
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/thread/mutex.hpp>
 
 namespace Orthanc
 {
-  class TimeoutDicomConnectionManager : public IDicomConnectionManager
-  {
-  public:
-    void SetTimeout(unsigned int timeout)
-    {
-    }
-
-    unsigned int GetTimeout()
-    {
-      return 0;
-    }
-
-    void Close()
-    {
-    }
-
-    void CheckTimeout()
-    {
-    }
-  };
-}
-
-#else
-
-#include "../Compatibility.h"
-
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-namespace Orthanc
-{
-  class TimeoutDicomConnectionManager : public IDicomConnectionManager
+  /**
+   * This class corresponds to a singleton to a DICOM SCU connection.
+   **/
+  class TimeoutDicomConnectionManager : public boost::noncopyable
   {
   private:
-    class Resource;
-
+    boost::mutex                          mutex_;
     std::unique_ptr<DicomUserConnection>  connection_;
     boost::posix_time::ptime              lastUse_;
     boost::posix_time::time_duration      timeout_;
 
-    void Touch();
+    // Mutex must be locked
+    void TouchInternal();
 
-    void CheckTimeoutInternal();
+    // Mutex must be locked
+    void OpenInternal(const std::string& localAet,
+                      const RemoteModalityParameters& remote);
+
+    // Mutex must be locked
+    void CloseInternal();
 
   public:
+    class Lock : public boost::noncopyable
+    {
+    private:
+      TimeoutDicomConnectionManager&  that_;
+      boost::mutex::scoped_lock       lock_;
+
+    public:
+      Lock(TimeoutDicomConnectionManager& that,
+           const std::string& localAet,
+           const RemoteModalityParameters& remote);
+      
+      ~Lock();
+
+      DicomUserConnection& GetConnection();
+    };
+
     TimeoutDicomConnectionManager() :
       timeout_(boost::posix_time::milliseconds(1000))
     {
     }
 
-    void SetTimeout(unsigned int timeout);
+    void SetInactivityTimeout(unsigned int milliseconds);
 
-    unsigned int GetTimeout();
+    unsigned int GetInactivityTimeout();  // In milliseconds
 
     void Close();
 
-    void CheckTimeout();
-
-    virtual IResource* AcquireConnection(const std::string& localAet,
-                                         const RemoteModalityParameters& remote);
+    void CloseIfInactive();
   };
 }
-
-#endif
--- a/Core/DicomParsing/ParsedDicomFile.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -952,7 +952,7 @@
        * equals the empty string, then proceed. In Orthanc <= 1.5.6,
        * an exception "Bad file format" was generated.
        * https://groups.google.com/d/msg/orthanc-users/aphG_h1AHVg/rfOTtTPTAgAJ
-       * https://bitbucket.org/sjodogne/orthanc/commits/4c45e018bd3de3cfa21d6efc6734673aaaee4435
+       * https://hg.orthanc-server.com/orthanc/rev/4c45e018bd3de3cfa21d6efc6734673aaaee4435
        **/
       patientId.clear();
     }        
--- a/Core/JobsEngine/Operations/IJobOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/JobsEngine/Operations/IJobOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -34,7 +34,6 @@
 #pragma once
 
 #include "JobOperationValues.h"
-#include "../../DicomNetworking/IDicomConnectionManager.h"
 
 namespace Orthanc
 {
@@ -46,8 +45,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& dicomConnection) = 0;
+                       const JobOperationValue& input) = 0;
 
     virtual void Serialize(Json::Value& result) const = 0;
   };
--- a/Core/JobsEngine/Operations/LogJobOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -40,8 +40,7 @@
 namespace Orthanc
 {
   void LogJobOperation::Apply(JobOperationValues& outputs,
-                              const JobOperationValue& input,
-                              IDicomConnectionManager& connectionManager)
+                              const JobOperationValue& input)
   {
     switch (input.GetType())
     {
--- a/Core/JobsEngine/Operations/LogJobOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -41,8 +41,7 @@
   {
   public:
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const
     {
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -43,7 +43,6 @@
 {
   static const char* CURRENT = "Current";
   static const char* DESCRIPTION = "Description";
-  static const char* DICOM_TIMEOUT = "DicomTimeout";
   static const char* NEXT_OPERATIONS = "Next";
   static const char* OPERATION = "Operation";
   static const char* OPERATIONS = "Operations";
@@ -127,7 +126,7 @@
       return currentInput_ >= originalInputs_->GetSize() + workInputs_->GetSize();
     }
 
-    void Step(IDicomConnectionManager& connectionManager)
+    void Step()
     {
       if (IsDone())
       {
@@ -146,7 +145,7 @@
       }
 
       JobOperationValues outputs;
-      operation_->Apply(outputs, *input, connectionManager);
+      operation_->Apply(outputs, *input);
 
       if (!nextOperations_.empty())
       {
@@ -254,12 +253,6 @@
   }
 
   
-  void SequenceOfOperationsJob::Lock::SetDicomAssociationTimeout(unsigned int timeout)
-  {
-    that_.connectionManager_.SetTimeout(timeout);
-  }
-
-
   size_t SequenceOfOperationsJob::Lock::AddOperation(IJobOperation* operation)
   {
     if (IsDone())
@@ -341,7 +334,6 @@
           (*it)->SignalDone(*this);
         }
 
-        connectionManager_.Close();
         return JobStepResult::Success();
       }
       else
@@ -360,11 +352,9 @@
 
     if (current_ < operations_.size())
     {
-      operations_[current_]->Step(connectionManager_);
+      operations_[current_]->Step();
     }
 
-    connectionManager_.CheckTimeout();
-
     return JobStepResult::Continue();
   }
 
@@ -383,13 +373,6 @@
   }
 
 
-  void SequenceOfOperationsJob::Stop(JobStopReason reason)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    connectionManager_.Close();
-  }
-
-
   float SequenceOfOperationsJob::GetProgress()
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -420,7 +403,6 @@
     
     value[DESCRIPTION] = description_;
     value[TRAILING_TIMEOUT] = static_cast<unsigned int>(trailingTimeout_.total_milliseconds());
-    value[DICOM_TIMEOUT] = connectionManager_.GetTimeout();
     value[CURRENT] = static_cast<unsigned int>(current_);
     
     Json::Value tmp = Json::arrayValue;
@@ -454,8 +436,6 @@
     description_ = SerializationToolbox::ReadString(serialized, DESCRIPTION);
     trailingTimeout_ = boost::posix_time::milliseconds
       (SerializationToolbox::ReadUnsignedInteger(serialized, TRAILING_TIMEOUT));
-    connectionManager_.SetTimeout
-      (SerializationToolbox::ReadUnsignedInteger(serialized, DICOM_TIMEOUT));
     current_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT);
 
     const Json::Value& ops = serialized[OPERATIONS];
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Tue Apr 21 16:33:59 2020 +0200
@@ -36,8 +36,6 @@
 #include "../IJob.h"
 #include "IJobOperation.h"
 
-#include "../../DicomNetworking/TimeoutDicomConnectionManager.h"
-
 #include <boost/thread/mutex.hpp>
 #include <boost/thread/condition_variable.hpp>
 
@@ -69,7 +67,6 @@
     boost::condition_variable         operationAdded_;
     boost::posix_time::time_duration  trailingTimeout_;
     std::list<IObserver*>             observers_;
-    TimeoutDicomConnectionManager     connectionManager_;
 
     void NotifyDone() const;
 
@@ -109,8 +106,6 @@
       }
 
       void SetTrailingOperationTimeout(unsigned int timeout);
-
-      void SetDicomAssociationTimeout(unsigned int timeout);
       
       size_t AddOperation(IJobOperation* operation);
 
@@ -134,7 +129,9 @@
 
     virtual void Reset() ORTHANC_OVERRIDE;
 
-    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
+    {
+    }
 
     virtual float GetProgress() ORTHANC_OVERRIDE;
 
--- a/NEWS	Mon Apr 20 14:04:14 2020 +0200
+++ b/NEWS	Tue Apr 21 16:33:59 2020 +0200
@@ -2,6 +2,9 @@
 ===============================
 
 
+Version 1.6.1 (2020-04-21)
+==========================
+
 REST API
 --------
 
@@ -34,7 +37,6 @@
 * Error reporting on failure while initializing SSL
 * Fix unit test ParsedDicomFile.ToJsonFlags2 on big-endian architectures
 * Avoid one memcpy of the DICOM buffer on "POST /instances"
-* Default value of "HttpThreadsCount" reduced from 50 to 10
 * Upgraded dependencies for static builds (notably on Windows):
   - civetweb 1.12
   - openssl 1.1.1f
--- a/OrthancServer/LuaScripting.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -772,6 +772,8 @@
           LOG(ERROR) << "Error while processing Lua events: " << e.What();
         }
       }
+
+      that->jobManager_.GetDicomConnectionManager().CloseIfInactive();
     }
   }
 
--- a/OrthancServer/LuaScripting.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/LuaScripting.h	Tue Apr 21 16:33:59 2020 +0200
@@ -136,5 +136,10 @@
     void SignalJobSuccess(const std::string& jobId);
 
     void SignalJobFailure(const std::string& jobId);
+
+    TimeoutDicomConnectionManager& GetDicomConnectionManager()
+    {
+      return jobManager_.GetDicomConnectionManager();
+    }
   };
 }
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -68,13 +68,16 @@
     priority_(0),
     trailingTimeout_(5000)
   {
+    unsigned int dicomTimeout;
+    
     {
       OrthancConfiguration::ReaderLock lock;
-      dicomTimeout_ = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomAssociationCloseDelay", 5);
+      dicomTimeout = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomAssociationCloseDelay", 5);
     }
 
+    connectionManager_.SetInactivityTimeout(dicomTimeout * 1000);  // Milliseconds expected
     LOG(INFO) << "Lua: DICOM associations will be closed after "
-              << dicomTimeout_ << " seconds of inactivity";
+              << dicomTimeout << " seconds of inactivity";
   }
 
 
@@ -149,7 +152,6 @@
       {
         jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_));
         jobLock_->SetTrailingOperationTimeout(that_.trailingTimeout_);
-        jobLock_->SetDicomAssociationTimeout(that_.dicomTimeout_ * 1000);  // Milliseconds expected
       }
     }
 
@@ -202,7 +204,7 @@
                                                    const RemoteModalityParameters& modality)
   {
     assert(jobLock_.get() != NULL);
-    return jobLock_->AddOperation(new StoreScuOperation(localAet, modality));    
+    return jobLock_->AddOperation(new StoreScuOperation(that_.connectionManager_, localAet, modality));    
   }
 
 
--- a/OrthancServer/ServerJobs/LuaJobManager.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Tue Apr 21 16:33:59 2020 +0200
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Core/DicomNetworking/TimeoutDicomConnectionManager.h"
 #include "../../Core/DicomParsing/DicomModification.h"
 #include "../../Core/JobsEngine/JobsEngine.h"
 #include "../../Core/JobsEngine/Operations/SequenceOfOperationsJob.h"
@@ -51,7 +52,7 @@
     size_t                    maxOperations_;
     int                       priority_;
     unsigned int              trailingTimeout_;
-    unsigned int              dicomTimeout_;
+    TimeoutDicomConnectionManager  connectionManager_;
 
     virtual void SignalDone(const SequenceOfOperationsJob& job);
 
@@ -66,6 +67,11 @@
 
     void AwakeTrailingSleep();
 
+    TimeoutDicomConnectionManager& GetDicomConnectionManager()
+    {
+      return connectionManager_;
+    }
+
     class Lock : public boost::noncopyable
     {
     private:
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -43,8 +43,7 @@
 namespace Orthanc
 {
   void DeleteResourceOperation::Apply(JobOperationValues& outputs,
-                                      const JobOperationValue& input,
-                                      IDicomConnectionManager& connectionManager)
+                                      const JobOperationValue& input)
   {
     switch (input.GetType())
     {
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -51,8 +51,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const
     {
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -81,8 +81,7 @@
   }
 
   void ModifyInstanceOperation::Apply(JobOperationValues& outputs,
-                                      const JobOperationValue& input,
-                                      IDicomConnectionManager& connectionManager)
+                                      const JobOperationValue& input)
   {
     if (input.GetType() != JobOperationValue::Type_DicomInstance)
     {
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -67,8 +67,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& target) const;
   };
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -44,8 +44,7 @@
 namespace Orthanc
 {
   void StorePeerOperation::Apply(JobOperationValues& outputs,
-                                 const JobOperationValue& input,
-                                 IDicomConnectionManager& connectionManager)
+                                 const JobOperationValue& input)
   {
     // Configure the HTTP client
     HttpClient client(peer_, "instances");
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -57,8 +57,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -43,18 +43,10 @@
 namespace Orthanc
 {
   void StoreScuOperation::Apply(JobOperationValues& outputs,
-                                const JobOperationValue& input,
-                                IDicomConnectionManager& connectionManager)
+                                const JobOperationValue& input)
   {
-    std::unique_ptr<IDicomConnectionManager::IResource> resource
-      (connectionManager.AcquireConnection(localAet_, modality_));
-
-    if (resource.get() == NULL)
-    {
-      LOG(ERROR) << "Lua: Cannot connect to modality: " << modality_.GetApplicationEntityTitle();
-      return;
-    }
-
+    TimeoutDicomConnectionManager::Lock lock(connectionManager_, localAet_, modality_);
+    
     if (input.GetType() != JobOperationValue::Type_DicomInstance)
     {
       throw OrthancException(ErrorCode_BadParameterType);
@@ -72,7 +64,7 @@
       instance.ReadDicom(dicom);
 
       std::string sopClassUid, sopInstanceUid;  // Unused
-      resource->GetConnection().Store(sopClassUid, sopInstanceUid, dicom);
+      lock.GetConnection().Store(sopClassUid, sopInstanceUid, dicom);
     }
     catch (OrthancException& e)
     {
@@ -93,7 +85,9 @@
   }
 
 
-  StoreScuOperation::StoreScuOperation(const Json::Value& serialized)
+  StoreScuOperation::StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+                                       const Json::Value& serialized) :
+    connectionManager_(connectionManager)
   {
     if (SerializationToolbox::ReadString(serialized, "Type") != "StoreScu" ||
         !serialized.isMember("LocalAET"))
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -34,25 +34,29 @@
 #pragma once
 
 #include "../../../Core/JobsEngine/Operations/IJobOperation.h"
-#include "../../../Core/DicomNetworking/RemoteModalityParameters.h"
+#include "../../../Core/DicomNetworking/TimeoutDicomConnectionManager.h"
 
 namespace Orthanc
 {
   class StoreScuOperation : public IJobOperation
   {
   private:
-    std::string               localAet_;
-    RemoteModalityParameters  modality_;
+    TimeoutDicomConnectionManager&  connectionManager_;
+    std::string                     localAet_;
+    RemoteModalityParameters        modality_;
     
   public:
-    StoreScuOperation(const std::string& localAet,
+    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+                      const std::string& localAet,
                       const RemoteModalityParameters& modality) :
+      connectionManager_(connectionManager),
       localAet_(localAet),
       modality_(modality)
     {
     }
 
-    StoreScuOperation(const Json::Value& serialized);
+    StoreScuOperation(TimeoutDicomConnectionManager& connectionManager,
+                      const Json::Value& serialized);
 
     const std::string& GetLocalAet() const
     {
@@ -65,8 +69,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& manager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -74,8 +74,7 @@
 
 
   void SystemCallOperation::Apply(JobOperationValues& outputs,
-                                  const JobOperationValue& input,
-                                  IDicomConnectionManager& connectionManager)
+                                  const JobOperationValue& input)
   {
     std::vector<std::string> arguments = preArguments_;
 
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Tue Apr 21 16:33:59 2020 +0200
@@ -93,8 +93,7 @@
     const std::string& GetPostArgument(size_t i) const;
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       IDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -126,7 +126,8 @@
     }
     else if (type == "StoreScu")
     {
-      return new StoreScuOperation(source);
+      return new StoreScuOperation(
+        context_.GetLuaScripting().GetDicomConnectionManager(), source);
     }
     else if (type == "SystemCall")
     {
--- a/OrthancServer/main.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/OrthancServer/main.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -906,7 +906,7 @@
       httpDescribeErrors = lock.GetConfiguration().GetBooleanParameter("HttpDescribeErrors", true);
   
       // HTTP server
-      httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 10));
+      httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 50));
       httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042));
       httpServer.SetRemoteAccessAllowed(lock.GetConfiguration().GetBooleanParameter("RemoteAccessAllowed", false));
       httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", defaultKeepAlive));
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Apr 20 14:04:14 2020 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Apr 21 16:33:59 2020 +0200
@@ -180,7 +180,7 @@
 /**
  * For Microsoft Visual Studio, a compatibility "stdint.h" can be
  * downloaded at the following URL:
- * https://bitbucket.org/sjodogne/orthanc/raw/default/Resources/ThirdParty/VisualStudio/stdint.h
+ * https://hg.orthanc-server.com/orthanc/raw-file/tip/Resources/ThirdParty/VisualStudio/stdint.h
  **/
 #include <stdint.h>
 
--- a/Plugins/Samples/Basic/Plugin.c	Mon Apr 20 14:04:14 2020 +0200
+++ b/Plugins/Samples/Basic/Plugin.c	Tue Apr 21 16:33:59 2020 +0200
@@ -446,7 +446,7 @@
   sprintf(buf, "Incoming has pixel data: %d", hasPixelData);
   OrthancPluginLogWarning(context, buf);
 
-  // Reject all instances without pixel data
+  /* Reject all instances without pixel data */
   return hasPixelData;
 }
 
--- a/Resources/Configuration.json	Mon Apr 20 14:04:14 2020 +0200
+++ b/Resources/Configuration.json	Tue Apr 21 16:33:59 2020 +0200
@@ -391,11 +391,8 @@
   // caveats: https://eklitzke.org/the-caveats-of-tcp-nodelay
   "TcpNoDelay" : true,
 
-  // Number of threads that are used by the embedded HTTP server.  In
-  // Orthanc <= 1.6.0, the default value was 50. In Orthanc >= 1.6.1,
-  // default is 10 to prevent memory grow in basic setups.
-  // https://groups.google.com/d/msg/orthanc-users/qWqxpvCPv8g/Z8huoA5FDAAJ
-  "HttpThreadsCount" : 10,
+  // Number of threads that are used by the embedded HTTP server.
+  "HttpThreadsCount" : 50,
 
   // If this option is set to "false", Orthanc will run in index-only
   // mode. The DICOM files will not be stored on the drive. Note that
@@ -406,7 +403,7 @@
   // as new DICOM commands are issued. This option sets the number of
   // seconds of inactivity to wait before automatically closing a
   // DICOM association used by Lua. If set to 0, the connection is
-  // closed immediately.
+  // closed immediately. This option is only used in Lua scripts.
   "DicomAssociationCloseDelay" : 5,
 
   // Maximum number of query/retrieve DICOM requests that are
--- a/Resources/DownloadOrthancFramework.cmake	Mon Apr 20 14:04:14 2020 +0200
+++ b/Resources/DownloadOrthancFramework.cmake	Tue Apr 21 16:33:59 2020 +0200
@@ -114,6 +114,8 @@
         set(ORTHANC_FRAMEWORK_MD5 "82323e8c49a667f658a3639ea4dbc336")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.0")
         set(ORTHANC_FRAMEWORK_MD5 "eab428d6e53f61e847fa360bb17ebe25")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.6.1")
+        set(ORTHANC_FRAMEWORK_MD5 "3971f5de96ba71dc9d3f3690afeaa7c0")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -216,7 +218,7 @@
   else()
     message("Forking the Orthanc source repository using Mercurial")
     execute_process(
-      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://bitbucket.org/sjodogne/orthanc"
+      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://hg.orthanc-server.com/orthanc/"
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       RESULT_VARIABLE Failure
       )    
--- a/Resources/Samples/README.txt	Mon Apr 20 14:04:14 2020 +0200
+++ b/Resources/Samples/README.txt	Tue Apr 21 16:33:59 2020 +0200
@@ -2,6 +2,6 @@
 the "OrthancContributed" repository on GitHub:
 https://github.com/jodogne/OrthancContributed
 
-The integration tests of Orthanc provide a lot of samples about the
+The integration tests of Orthanc provide many samples about the
 features of the REST API of Orthanc:
-https://bitbucket.org/sjodogne/orthanc-tests/src/default/Tests/Tests.py
+https://hg.orthanc-server.com/orthanc-tests/file/tip/Tests/Tests.py
--- a/UnitTestsSources/MultiThreadingTests.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -1098,7 +1098,6 @@
       StringOperationValue s2("world");
       lock.AddInput(a, s1);
       lock.AddInput(a, s2);
-      lock.SetDicomAssociationTimeout(200);
       lock.SetTrailingOperationTimeout(300);
     }
 
@@ -1284,7 +1283,6 @@
     MemoryStorageArea              storage_;
     SQLiteDatabaseWrapper          db_;   // The SQLite DB is in memory
     std::unique_ptr<ServerContext>   context_;
-    TimeoutDicomConnectionManager  manager_;
 
   public:
     OrthancJobsSerialization()
@@ -1407,27 +1405,31 @@
   // StoreScuOperation
 
   {
-    RemoteModalityParameters modality;
-    modality.SetApplicationEntityTitle("REMOTE");
-    modality.SetHost("192.168.1.1");
-    modality.SetPortNumber(1000);
-    modality.SetManufacturer(ModalityManufacturer_StoreScp);
+    TimeoutDicomConnectionManager luaManager;
+    
+    {
+      RemoteModalityParameters modality;
+      modality.SetApplicationEntityTitle("REMOTE");
+      modality.SetHost("192.168.1.1");
+      modality.SetPortNumber(1000);
+      modality.SetManufacturer(ModalityManufacturer_StoreScp);
 
-    StoreScuOperation operation("TEST", modality);
+      StoreScuOperation operation(luaManager, "TEST", modality);
 
-    ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
-    operation.Serialize(s);
-  }
+      ASSERT_TRUE(CheckIdempotentSerialization(unserializer, operation));
+      operation.Serialize(s);
+    }
 
-  {
-    operation.reset(unserializer.UnserializeOperation(s));
+    {
+      operation.reset(unserializer.UnserializeOperation(s));
 
-    const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation);
-    ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle());
-    ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost());
-    ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber());
-    ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer());
-    ASSERT_EQ("TEST", tmp.GetLocalAet());
+      const StoreScuOperation& tmp = dynamic_cast<StoreScuOperation&>(*operation);
+      ASSERT_EQ("REMOTE", tmp.GetRemoteModality().GetApplicationEntityTitle());
+      ASSERT_EQ("192.168.1.1", tmp.GetRemoteModality().GetHost());
+      ASSERT_EQ(1000, tmp.GetRemoteModality().GetPortNumber());
+      ASSERT_EQ(ModalityManufacturer_StoreScp, tmp.GetRemoteModality().GetManufacturer());
+      ASSERT_EQ("TEST", tmp.GetLocalAet());
+    }
   }
 
   // SystemCallOperation
--- a/UnitTestsSources/RestApiTests.cpp	Mon Apr 20 14:04:14 2020 +0200
+++ b/UnitTestsSources/RestApiTests.cpp	Tue Apr 21 16:33:59 2020 +0200
@@ -121,22 +121,26 @@
   HttpClient c;
   c.SetHttpsVerifyPeers(true);
   c.SetHttpsCACertificates("UnitTestsResults/bitbucket.cert");
-  c.SetUrl("https://bitbucket.org/sjodogne/orthanc/raw/Orthanc-0.9.3/Resources/Configuration.json");
+
+  // Test file modified on 2020-04-20, in order to use a git
+  // repository on BitBucket instead of a Mercurial repository
+  // (because Mercurial support disappears on 2020-05-31)
+  c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
 
   Json::Value v;
   c.Apply(v);
-  ASSERT_TRUE(v.isMember("LuaScripts"));
+  ASSERT_TRUE(v.isMember("ServeFolders"));
 }
 
 TEST(HttpClient, SslNoVerification)
 {
   HttpClient c;
   c.SetHttpsVerifyPeers(false);
-  c.SetUrl("https://bitbucket.org/sjodogne/orthanc/raw/Orthanc-0.9.3/Resources/Configuration.json");
+  c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
 
   Json::Value v;
   c.Apply(v);
-  ASSERT_TRUE(v.isMember("LuaScripts"));
+  ASSERT_TRUE(v.isMember("ServeFolders"));
 }
 
 #endif