# HG changeset patch # User Alain Mazy # Date 1789995735 -7200 # Node ID 2691260b08c5b64b0e10db90010ed868661a2415 # Parent fc60db3d268116124567955e01e20b0bb4befb79 reduced memory usage diff -r fc60db3d2681 -r 2691260b08c5 CMakeLists.txt --- 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 diff -r fc60db3d2681 -r 2691260b08c5 Framework/DownloadArea.cpp --- 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 #include +#include 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()); + } + } } diff -r fc60db3d2681 -r 2691260b08c5 NEWS --- 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) ======================== diff -r fc60db3d2681 -r 2691260b08c5 UnitTests/UnitTestsMain.cpp --- 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((i * 131 + 7) % 256); + } + + std::string md5; + Orthanc::Toolbox::ComputeMD5(md5, s); + + std::vector 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) {