BUG: Remove unnecessary global fflush(nullptr) causing MetaIO read deadlocks - #6833
Conversation
…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.
|
Use fixed, moderate thread/file/repeat/timeout counts instead of scaling nThreads off std::thread::hardware_concurrency(), which spawned up to ~70+ threads on many-core machines. Verified the reduced load (16 threads, 16 files, 4 repeats, 60s timeout) still reliably reproduces the MetaIO concurrency hang fixed by ITK PR InsightSoftwareConsortium/ITK#6833 when run against unpatched ITK, while completing in a few seconds against patched ITK.
|
|
||
| MetaObject::M_Destroy(); | ||
|
|
||
| fflush(nullptr); |
There was a problem hiding this comment.
@blowekamp Just wondering, will you also make a PR to fix this upstream? At https://github.com/Kitware/MetaIO/blob/253d202b5a8595dd05312bf7656542a091486ebe/src/metaObject.cxx#L360
There was a problem hiding this comment.
For the record, this fflush call was already there in https://github.com/Kitware/MetaIO/blob/bd09e2993574d7b7f2890038f8dffde4a07d7160/src/metaObject.cxx#L143 as added with commit Kitware/MetaIO@bd09e29, @aylward, Aug 6, 2002.
|
@N-Dekker The fix has been ported upstream. |
…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. Ported from InsightSoftwareConsortium/ITK#6833. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Superseded by: #6835 |
…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 #6833).
Summary
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