# HG changeset patch # User James # Date 1789524807 -36000 # Wed Sep 16 12:13:27 2026 +1000 # Node ID b65e5daebbd67647bc001393e939b7544f21e945 # Parent d57101daf2a558c900eb63b7cde6e14763cc84f5 stream MD5 and mmap the file when committing transferred instances, to cut peak memory during commit diff -r d57101daf2a5 -r b65e5daebbd6 CMakeLists.txt --- a/CMakeLists.txt Wed Sep 16 10:59:27 2026 +1000 +++ b/CMakeLists.txt Wed Sep 16 12:13:27 2026 +1000 @@ -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 d57101daf2a5 -r b65e5daebbd6 Framework/DownloadArea.cpp --- a/Framework/DownloadArea.cpp Wed Sep 16 10:59:27 2026 +1000 +++ b/Framework/DownloadArea.cpp Wed Sep 16 12:13:27 2026 +1000 @@ -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 d57101daf2a5 -r b65e5daebbd6 UnitTests/UnitTestsMain.cpp --- a/UnitTests/UnitTestsMain.cpp Wed Sep 16 10:59:27 2026 +1000 +++ b/UnitTests/UnitTestsMain.cpp Wed Sep 16 12:13:27 2026 +1000 @@ -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) {