# HG changeset patch # User Sebastien Jodogne # Date 1398858581 -7200 # Node ID 3f946e5c3802d3ff36417a049d371785ab1b9090 # Parent 0a2f8c707c782139f761206a240812efd4bdbba3 ReusableDicomUserConnection diff -r 0a2f8c707c78 -r 3f946e5c3802 CMakeLists.txt --- 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 diff -r 0a2f8c707c78 -r 3f946e5c3802 Core/MultiThreading/ILockable.h --- 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; }; } diff -r 0a2f8c707c78 -r 3f946e5c3802 Core/MultiThreading/Locker.h --- 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_; diff -r 0a2f8c707c78 -r 3f946e5c3802 Core/MultiThreading/Mutex.h --- 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(); }; } diff -r 0a2f8c707c78 -r 3f946e5c3802 Core/MultiThreading/ReaderWriterLock.cpp --- 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) + { + } + }; } diff -r 0a2f8c707c78 -r 3f946e5c3802 OrthancServer/DicomProtocol/DicomUserConnection.cpp --- 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)); diff -r 0a2f8c707c78 -r 3f946e5c3802 OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp --- /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 . + **/ + + +#include "ReusableDicomUserConnection.h" + +#include "../../Core/OrthancException.h" + +#include + +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(); + } +} + diff -r 0a2f8c707c78 -r 3f946e5c3802 OrthancServer/DicomProtocol/ReusableDicomUserConnection.h --- /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 . + **/ + + +#pragma once + +#include "DicomUserConnection.h" +#include "../../Core/MultiThreading/Locker.h" + +#include +#include + +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); + }; +} + diff -r 0a2f8c707c78 -r 3f946e5c3802 UnitTestsSources/MultiThreading.cpp --- 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 + #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); +}