Mercurial > hg > orthanc-transfers
changeset 115:2691260b08c5 default tip
reduced memory usage
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Mon, 21 Sep 2026 15:02:15 +0200 |
| parents | fc60db3d2681 |
| children | |
| files | CMakeLists.txt Framework/DownloadArea.cpp NEWS UnitTests/UnitTestsMain.cpp |
| diffstat | 4 files changed, 84 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Aug 14 08:49:57 2026 +0200 +++ b/CMakeLists.txt Mon Sep 21 15:02:15 2026 +0200 @@ -143,6 +143,13 @@ Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp ) +if (STATIC_BUILD) + # Needed by Framework/DownloadArea.cpp for boost::iostreams::mapped_file_source: + # the framework's own static-build source list only compiles file_descriptor.cpp, + # as this plugin is the first to need memory-mapped files. + list(APPEND FRAMEWORK_SOURCES ${BOOST_SOURCES_DIR}/libs/iostreams/src/mapped_file.cpp) +endif() + add_library(OrthancTransfers SHARED
--- a/Framework/DownloadArea.cpp Fri Aug 14 08:49:57 2026 +0200 +++ b/Framework/DownloadArea.cpp Mon Sep 21 15:02:15 2026 +0200 @@ -29,6 +29,7 @@ #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> +#include <boost/iostreams/device/mapped_file.hpp> namespace OrthancPlugins { @@ -140,32 +141,59 @@ } } - void DownloadArea::Instance::Commit(bool simulate) const { - std::string content; - Orthanc::SystemToolbox::ReadFile(content, file_.GetPath()); - + // Streams the file in fixed-size chunks instead of reading it fully + // into a heap-allocated std::string first (this matters for large + // instances: that buffer is anonymous memory, which the kernel can + // only reclaim under pressure by swapping it out). std::string md5; - Orthanc::Toolbox::ComputeMD5(md5, content); + Orthanc::SystemToolbox::ComputeFileMD5(md5, file_.GetPath()); - if (md5 == info_.GetMD5()) - { - if (!simulate) - { - Json::Value result; - if (!RestApiPost(result, "/instances", - content.empty() ? NULL : content.c_str(), content.size(), - false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile, "Cannot import a transfered DICOM instance into Orthanc: " + info_.GetId()); - } - } - } - else + if (md5 != info_.GetMD5()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile, "Bad MD5 sum in a transfered DICOM instance: " + info_.GetId()); } + + if (!simulate) + { + Json::Value result; + bool success; + + if (info_.GetSize() == 0) + { + success = RestApiPost(result, "/instances", NULL, 0, false); + } + else + { + // Memory-map the file instead of copying it into a std::string: + // the mapped pages are file-backed, so under memory pressure the + // kernel can drop them instantly (no swap write-back needed), and + // there is no read()-style kernel-to-userspace copy either. + boost::iostreams::mapped_file_source mapped; + + try + { + mapped.open(file_.GetPath()); + } + catch (const std::exception&) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile, "Cannot memory-map a transfered DICOM instance: " + info_.GetId()); + } + + if (!mapped.is_open()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile, "Cannot memory-map a transfered DICOM instance: " + info_.GetId()); + } + + success = RestApiPost(result, "/instances", mapped.data(), mapped.size(), false); + } + + if (!success) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile, "Cannot import a transfered DICOM instance into Orthanc: " + info_.GetId()); + } + } }
--- a/NEWS Fri Aug 14 08:49:57 2026 +0200 +++ b/NEWS Mon Sep 21 15:02:15 2026 +0200 @@ -1,3 +1,8 @@ +Pending changes in the mainline +=============================== + +* reduced the memory consumption (https://discourse.orthanc-server.org/t/transfers-plugin-reduced-memory-usage-patch/6479). + Version 1.7 (2025-12-15) ========================
--- a/UnitTests/UnitTestsMain.cpp Fri Aug 14 08:49:57 2026 +0200 +++ b/UnitTests/UnitTestsMain.cpp Mon Sep 21 15:02:15 2026 +0200 @@ -431,6 +431,31 @@ } +TEST(DownloadArea, StreamingMD5AcrossChunkBoundary) +{ + using namespace OrthancPlugins; + + // Larger than DownloadArea's internal MD5 read-chunk size (1MB), and not + // a multiple of it, so an off-by-one in the chunked reading loop would + // corrupt the digest. + std::string s(3 * 1024 * 1024 + 12345, '\0'); + for (size_t i = 0; i < s.size(); i++) + { + s[i] = static_cast<char>((i * 131 + 7) % 256); + } + + std::string md5; + Orthanc::Toolbox::ComputeMD5(md5, s); + + std::vector<DicomInstanceInfo> instances; + instances.push_back(DicomInstanceInfo("large", s.size(), md5)); + + DownloadArea area(instances); + area.WriteInstance("large", s.c_str(), s.size()); + area.CheckMD5(); +} + + int main(int argc, char **argv) {
