…adlocks
MetaObject::ReadStream() and MetaForm::ReadStream() unconditionally call
fflush(nullptr) before parsing, a leftover from the original MetaIO commit
that predates any multithreading concerns for this library. fflush(NULL)
flushes every open FILE* stream in the entire process, not just the one
being read.
On platforms such as macOS, this requires walking the process-wide list of
all open FILE* streams and locking each one in turn. Under heavy concurrent
MetaImage/MHA reads (many threads each opening/reading/closing their own
files), this global flush-all creates severe lock contention that can grind
concurrent reads to a halt indefinitely, since each thread's fflush(nullptr)
call competes to lock every other thread's in-flight FILE* handle.
Reading does not require flushing any previously written data, so this call
serves no purpose and can simply be removed.
Backport of the same fix on main (PR InsightSoftwareConsortium#6833).
Summary
Backport of #6833 to release-5.4.
MetaObject::ReadStream() and MetaForm::ReadStream() unconditionally call fflush(nullptr) before parsing header fields. This line dates back to MetaIOs very first file-IO commit, long before multithreading was a design concern for this library.
fflush(NULL) flushes every open FILE* stream in the entire process, not just the one being read. On platforms such as macOS, this requires walking the process-wide list of all open FILE* streams and locking each one in turn (_fwalk -> sflush_locked -> flockfile).
Under heavy concurrent MetaImage/.mha reads (many threads, each opening/reading/closing its own file), this global flush-all creates severe lock contention: every threads fflush(nullptr) call competes to lock every other threads in-flight FILE* handle. In practice this can grind concurrent reads to a halt indefinitely (observed via a native C++ gtest reproducer with 70+ threads reading distinct .mha files concurrently; lldb backtraces showed the large majority of threads parked in flockfile/sflush_locked/_fwalk called from MetaObject::ReadStream).
Reading a file does not require flushing any previously written data (nothing was just written), so this call serves no purpose and can simply be removed.
Test plan