changeset 3851:6498739a3c3c

refactoring: TimeoutDicomConnectionManager is now only used by Lua
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 20 Apr 2020 16:46:44 +0200
parents d729d6e8b484
children ee0a1211419f
files Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Core/DicomNetworking/TimeoutDicomConnectionManager.h Core/JobsEngine/Operations/IJobOperation.h Core/JobsEngine/Operations/LogJobOperation.cpp Core/JobsEngine/Operations/LogJobOperation.h Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Core/JobsEngine/Operations/SequenceOfOperationsJob.h OrthancServer/LuaScripting.cpp OrthancServer/LuaScripting.h OrthancServer/ServerJobs/LuaJobManager.cpp OrthancServer/ServerJobs/LuaJobManager.h OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp OrthancServer/ServerJobs/Operations/StorePeerOperation.h OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp OrthancServer/ServerJobs/Operations/StoreScuOperation.h OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp OrthancServer/ServerJobs/Operations/SystemCallOperation.h OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Resources/Configuration.json UnitTestsSources/MultiThreadingTests.cpp
diffstat 24 files changed, 153 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -45,59 +45,58 @@
   }
 
 
-  TimeoutDicomConnectionManager::Resource::Resource(TimeoutDicomConnectionManager& that) : 
-    that_(that)
+  TimeoutDicomConnectionManager::Lock::Lock(TimeoutDicomConnectionManager& that,
+                                            const std::string& localAet,
+                                            const RemoteModalityParameters& remote) : 
+    that_(that),
+    lock_(that_.mutex_)
   {
-    if (that_.connection_.get() == NULL)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
+    // Calling "Touch()" will be done by the "~Lock()" destructor
+    that_.OpenInternal(localAet, remote);
+  }
+
+  
+  TimeoutDicomConnectionManager::Lock::~Lock()
+  {
+    that_.TouchInternal();
   }
 
   
-  TimeoutDicomConnectionManager::Resource::~Resource()
+  DicomUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection()
   {
-    that_.Touch();
-  }
-
-  
-  DicomUserConnection& TimeoutDicomConnectionManager::Resource::GetConnection()
-  {
-    assert(that_.connection_.get() != NULL);
-    return *that_.connection_;
+    if (that_.connection_.get() == NULL)
+    {
+      // The allocation should have been done by "that_.Open()" in the constructor
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      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)
     {
@@ -109,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();
   }
 
 
-  TimeoutDicomConnectionManager::Resource* 
-  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:45:21 2020 +0200
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Mon Apr 20 16:46:44 2020 +0200
@@ -37,62 +37,53 @@
 #  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined
 #endif
 
-#if ORTHANC_ENABLE_DCMTK_NETWORKING == 0
-
-namespace Orthanc
-{
-  class TimeoutDicomConnectionManager : public boost::noncopyable
-  {
-  public:
-    void SetTimeout(unsigned int timeout)
-    {
-    }
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be 1 to use this file
+#endif
 
-    unsigned int GetTimeout()
-    {
-      return 0;
-    }
-
-    void Close()
-    {
-    }
-
-    void CheckTimeout()
-    {
-    }
-  };
-}
-
-#else
 
 #include "../Compatibility.h"
 #include "DicomUserConnection.h"
 
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/thread/mutex.hpp>
 
 namespace Orthanc
 {
+  /**
+   * This class corresponds to a singleton to a DICOM SCU connection.
+   **/
   class TimeoutDicomConnectionManager : public boost::noncopyable
   {
   private:
+    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 Resource : public boost::noncopyable
+    class Lock : public boost::noncopyable
     {
     private:
       TimeoutDicomConnectionManager&  that_;
+      boost::mutex::scoped_lock       lock_;
 
     public:
-      Resource(TimeoutDicomConnectionManager& that);
+      Lock(TimeoutDicomConnectionManager& that,
+           const std::string& localAet,
+           const RemoteModalityParameters& remote);
       
-      ~Resource();
+      ~Lock();
 
       DicomUserConnection& GetConnection();
     };
@@ -102,17 +93,12 @@
     {
     }
 
-    void SetTimeout(unsigned int timeout);
+    void SetInactivityTimeout(unsigned int milliseconds);
 
-    unsigned int GetTimeout();
+    unsigned int GetInactivityTimeout();  // In milliseconds
 
     void Close();
 
-    void CheckTimeout();
-
-    Resource* AcquireConnection(const std::string& localAet,
-                                const RemoteModalityParameters& remote);
+    void CloseIfInactive();
   };
 }
-
-#endif
--- a/Core/JobsEngine/Operations/IJobOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/Core/JobsEngine/Operations/IJobOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -34,7 +34,6 @@
 #pragma once
 
 #include "JobOperationValues.h"
-#include "../../DicomNetworking/TimeoutDicomConnectionManager.h"
 
 namespace Orthanc
 {
@@ -46,8 +45,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& dicomConnection) = 0;
+                       const JobOperationValue& input) = 0;
 
     virtual void Serialize(Json::Value& result) const = 0;
   };
--- a/Core/JobsEngine/Operations/LogJobOperation.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -40,8 +40,7 @@
 namespace Orthanc
 {
   void LogJobOperation::Apply(JobOperationValues& outputs,
-                              const JobOperationValue& input,
-                              TimeoutDicomConnectionManager& connectionManager)
+                              const JobOperationValue& input)
   {
     switch (input.GetType())
     {
--- a/Core/JobsEngine/Operations/LogJobOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -41,8 +41,7 @@
   {
   public:
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const
     {
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Mon Apr 20 16:46:44 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(TimeoutDicomConnectionManager& 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:45:21 2020 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Mon Apr 20 16:46:44 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/OrthancServer/LuaScripting.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/LuaScripting.cpp	Mon Apr 20 16:46:44 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:45:21 2020 +0200
+++ b/OrthancServer/LuaScripting.h	Mon Apr 20 16:46:44 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:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Mon Apr 20 16:46:44 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:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Mon Apr 20 16:46:44 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:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -43,8 +43,7 @@
 namespace Orthanc
 {
   void DeleteResourceOperation::Apply(JobOperationValues& outputs,
-                                      const JobOperationValue& input,
-                                      TimeoutDicomConnectionManager& connectionManager)
+                                      const JobOperationValue& input)
   {
     switch (input.GetType())
     {
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -51,8 +51,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const
     {
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -81,8 +81,7 @@
   }
 
   void ModifyInstanceOperation::Apply(JobOperationValues& outputs,
-                                      const JobOperationValue& input,
-                                      TimeoutDicomConnectionManager& connectionManager)
+                                      const JobOperationValue& input)
   {
     if (input.GetType() != JobOperationValue::Type_DicomInstance)
     {
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -67,8 +67,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& target) const;
   };
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -44,8 +44,7 @@
 namespace Orthanc
 {
   void StorePeerOperation::Apply(JobOperationValues& outputs,
-                                 const JobOperationValue& input,
-                                 TimeoutDicomConnectionManager& connectionManager)
+                                 const JobOperationValue& input)
   {
     // Configure the HTTP client
     HttpClient client(peer_, "instances");
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -57,8 +57,7 @@
     }
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -43,18 +43,10 @@
 namespace Orthanc
 {
   void StoreScuOperation::Apply(JobOperationValues& outputs,
-                                const JobOperationValue& input,
-                                TimeoutDicomConnectionManager& connectionManager)
+                                const JobOperationValue& input)
   {
-    std::unique_ptr<TimeoutDicomConnectionManager::Resource> 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:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Mon Apr 20 16:46:44 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,
-                       TimeoutDicomConnectionManager& manager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -74,8 +74,7 @@
 
 
   void SystemCallOperation::Apply(JobOperationValues& outputs,
-                                  const JobOperationValue& input,
-                                  TimeoutDicomConnectionManager& connectionManager)
+                                  const JobOperationValue& input)
   {
     std::vector<std::string> arguments = preArguments_;
 
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Mon Apr 20 16:46:44 2020 +0200
@@ -93,8 +93,7 @@
     const std::string& GetPostArgument(size_t i) const;
 
     virtual void Apply(JobOperationValues& outputs,
-                       const JobOperationValue& input,
-                       TimeoutDicomConnectionManager& connectionManager);
+                       const JobOperationValue& input);
 
     virtual void Serialize(Json::Value& result) const;
   };
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Mon Apr 20 16:46:44 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/Resources/Configuration.json	Mon Apr 20 14:45:21 2020 +0200
+++ b/Resources/Configuration.json	Mon Apr 20 16:46:44 2020 +0200
@@ -406,7 +406,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/UnitTestsSources/MultiThreadingTests.cpp	Mon Apr 20 14:45:21 2020 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Mon Apr 20 16:46:44 2020 +0200
@@ -1098,7 +1098,6 @@
       StringOperationValue s2("world");
       lock.AddInput(a, s1);
       lock.AddInput(a, s2);
-      lock.SetDicomAssociationTimeout(200);
       lock.SetTrailingOperationTimeout(300);
     }
 
@@ -1406,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