changeset 769:3f946e5c3802

ReusableDicomUserConnection
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 30 Apr 2014 13:49:41 +0200
parents 0a2f8c707c78
children 537837f50fbb
files CMakeLists.txt Core/MultiThreading/ILockable.h Core/MultiThreading/Locker.h Core/MultiThreading/Mutex.h Core/MultiThreading/ReaderWriterLock.cpp OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/DicomProtocol/ReusableDicomUserConnection.h UnitTestsSources/MultiThreading.cpp
diffstat 9 files changed, 330 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Apr 29 17:37:19 2014 +0200
+++ b/CMakeLists.txt	Wed Apr 30 13:49:41 2014 +0200
@@ -203,6 +203,7 @@
   OrthancServer/DicomProtocol/DicomFindAnswers.cpp
   OrthancServer/DicomProtocol/DicomServer.cpp
   OrthancServer/DicomProtocol/DicomUserConnection.cpp
+  OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp
   OrthancServer/FromDcmtkBridge.cpp
   OrthancServer/Internals/CommandDispatcher.cpp
   OrthancServer/Internals/FindScp.cpp
--- a/Core/MultiThreading/ILockable.h	Tue Apr 29 17:37:19 2014 +0200
+++ b/Core/MultiThreading/ILockable.h	Wed Apr 30 13:49:41 2014 +0200
@@ -38,13 +38,16 @@
 {
   class ILockable : public boost::noncopyable
   {
+    friend class Locker;
+
+  protected:
+    virtual void Lock() = 0;
+
+    virtual void Unlock() = 0;
+
   public:
     virtual ~ILockable()
     {
     }
-
-    virtual void Lock() = 0;
-
-    virtual void Unlock() = 0;
   };
 }
--- a/Core/MultiThreading/Locker.h	Tue Apr 29 17:37:19 2014 +0200
+++ b/Core/MultiThreading/Locker.h	Wed Apr 30 13:49:41 2014 +0200
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class Locker
+  class Locker : public boost::noncopyable
   {
   private:
     ILockable& lockable_;
--- a/Core/MultiThreading/Mutex.h	Tue Apr 29 17:37:19 2014 +0200
+++ b/Core/MultiThreading/Mutex.h	Wed Apr 30 13:49:41 2014 +0200
@@ -43,13 +43,14 @@
 
     PImpl *pimpl_;
 
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
   public:
     Mutex();
 
     ~Mutex();
-
-    virtual void Lock();
-
-    virtual void Unlock();
   };
 }
--- a/Core/MultiThreading/ReaderWriterLock.cpp	Tue Apr 29 17:37:19 2014 +0200
+++ b/Core/MultiThreading/ReaderWriterLock.cpp	Wed Apr 30 13:49:41 2014 +0200
@@ -46,11 +46,7 @@
     private:
       boost::shared_mutex& lock_;
 
-    public:
-      ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-
+    protected:
       virtual void Lock()
       {
         lock_.lock_shared();
@@ -60,6 +56,11 @@
       {
         lock_.unlock_shared();        
       }
+
+    public:
+      ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
     };
 
 
@@ -68,11 +69,7 @@
     private:
       boost::shared_mutex& lock_;
 
-    public:
-      WriterLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-
+    protected:
       virtual void Lock()
       {
         lock_.lock();
@@ -82,6 +79,12 @@
       {
         lock_.unlock();        
       }
+
+    public:
+      WriterLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
+
     };
   }
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Apr 29 17:37:19 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Apr 30 13:49:41 2014 +0200
@@ -692,6 +692,11 @@
       return;
     }
 
+    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
+              << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host "
+              << GetDistantHost() << ":" << GetDistantPort() 
+              << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")";
+
     Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_));
     Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Wed Apr 30 13:49:41 2014 +0200
@@ -0,0 +1,167 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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/>.
+ **/
+
+
+#include "ReusableDicomUserConnection.h"
+
+#include "../../Core/OrthancException.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  static boost::posix_time::ptime Now()
+  {
+    return boost::posix_time::microsec_clock::local_time();
+  }
+
+  void ReusableDicomUserConnection::Open(const std::string& remoteAet,
+                                         const std::string& address,
+                                         int port,
+                                         ModalityManufacturer manufacturer)
+  {
+    if (connection_ != NULL &&
+        connection_->GetDistantApplicationEntityTitle() == remoteAet &&
+        connection_->GetDistantHost() == address &&
+        connection_->GetDistantPort() == port &&
+        connection_->GetDistantManufacturer() == manufacturer)
+    {
+      // The current connection can be reused
+      return;
+    }
+
+    Close();
+
+    connection_ = new DicomUserConnection();
+    connection_->SetLocalApplicationEntityTitle(localAet_);
+    connection_->SetDistantApplicationEntityTitle(remoteAet);
+    connection_->SetDistantHost(address);
+    connection_->SetDistantPort(port);
+    connection_->SetDistantManufacturer(manufacturer);
+    connection_->Open();
+  }
+    
+  void ReusableDicomUserConnection::Close()
+  {
+    if (connection_ != NULL)
+    {
+      delete connection_;
+      connection_ = NULL;
+    }
+  }
+
+  void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that)
+  {
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+      if (!that->continue_)
+      {
+        LOG(INFO) << "Finishing the thread watching the global SCU connection";
+        return;
+      }
+
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->connection_ != NULL &&
+            Now() > that->lastUse_ + that->timeBeforeClose_)
+        {
+          LOG(INFO) << "Closing the global SCU connection after timeout";
+          that->Close();
+        }
+      }
+    }
+  }
+    
+  ReusableDicomUserConnection::Connection::Connection
+  (ReusableDicomUserConnection& that,
+   const std::string& aet,
+   const std::string& address,
+   int port,
+   ModalityManufacturer manufacturer) : 
+    Locker(that)
+  {
+    that.Open(aet, address, port, manufacturer);
+    connection_ = that.connection_;
+  }
+
+  DicomUserConnection& ReusableDicomUserConnection::Connection::GetConnection()
+  {
+    if (connection_ == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return *connection_;
+  }      
+
+  ReusableDicomUserConnection::ReusableDicomUserConnection() : 
+    connection_(NULL), 
+    timeBeforeClose_(boost::posix_time::seconds(5)),  // By default, close connection after 5 seconds
+    localAet_("ORTHANC")
+  {
+    lastUse_ = Now();
+    continue_ = true;
+    closeThread_ = boost::thread(CloseThread, this);
+  }
+
+  ReusableDicomUserConnection::~ReusableDicomUserConnection()
+  {
+    continue_ = false;
+    closeThread_.join();
+    Close();
+  }
+
+  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(unsigned int ms)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    timeBeforeClose_ = boost::posix_time::milliseconds(ms);
+  }
+
+  void ReusableDicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    Close();
+    localAet_ = aet;
+  }
+
+  void ReusableDicomUserConnection::Lock()
+  {
+    mutex_.lock();
+  }
+
+  void ReusableDicomUserConnection::Unlock()
+  {
+    lastUse_ = Now();
+    mutex_.unlock();
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h	Wed Apr 30 13:49:41 2014 +0200
@@ -0,0 +1,100 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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
+
+#include "DicomUserConnection.h"
+#include "../../Core/MultiThreading/Locker.h"
+
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace Orthanc
+{
+  class ReusableDicomUserConnection : public ILockable
+  {
+  private:
+    boost::mutex mutex_;
+    DicomUserConnection* connection_;
+    bool continue_;
+    boost::posix_time::time_duration timeBeforeClose_;
+    boost::posix_time::ptime lastUse_;
+    boost::thread closeThread_;
+    std::string localAet_;
+
+    void Open(const std::string& remoteAet,
+              const std::string& address,
+              int port,
+              ModalityManufacturer manufacturer);
+    
+    void Close();
+
+    static void CloseThread(ReusableDicomUserConnection* that);
+
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
+  public:
+    class Connection : public Locker
+    {
+    private:
+      DicomUserConnection* connection_;
+
+    public:
+      Connection(ReusableDicomUserConnection& that,
+                 const std::string& aet,
+                 const std::string& address,
+                 int port,
+                 ModalityManufacturer manufacturer);
+
+      DicomUserConnection& GetConnection();
+    };
+
+    ReusableDicomUserConnection();
+
+    virtual ~ReusableDicomUserConnection();
+
+    unsigned int GetMillisecondsBeforeClose() const
+    {
+      return timeBeforeClose_.total_milliseconds();
+    }
+
+    void SetMillisecondsBeforeClose(unsigned int ms);
+
+    const std::string& GetLocalApplicationEntityTitle() const;
+
+    void SetLocalApplicationEntityTitle(const std::string& aet);
+  };
+}
+
--- a/UnitTestsSources/MultiThreading.cpp	Tue Apr 29 17:37:19 2014 +0200
+++ b/UnitTestsSources/MultiThreading.cpp	Wed Apr 30 13:49:41 2014 +0200
@@ -1,5 +1,7 @@
 #include "gtest/gtest.h"
 
+#include <glog/logging.h>
+
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
 #include "../Core/MultiThreading/ArrayFilledByThreads.h"
@@ -210,3 +212,32 @@
     Locker locker3(lock.ForWriter());
   }
 }
+
+
+
+
+
+#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h"
+
+TEST(ReusableDicomUserConnection, DISABLED_Basic)
+{
+  ReusableDicomUserConnection c;
+  c.SetMillisecondsBeforeClose(200);
+  printf("START\n"); fflush(stdout);
+  {
+    ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281");
+  }
+
+  printf("**\n"); fflush(stdout);
+  Toolbox::USleep(1000000);
+  printf("**\n"); fflush(stdout);
+
+  {
+    ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277");
+  }
+
+  Toolbox::ServerBarrier();
+  printf("DONE\n"); fflush(stdout);
+}