Skip to content

BUG: Remove unnecessary global fflush(nullptr) causing MetaIO read deadlocks - #146

Merged
dzenanz merged 1 commit into
Kitware:masterfrom
blowekamp:fix-fflush-deadlock
Sep 4, 2026
Merged

BUG: Remove unnecessary global fflush(nullptr) causing MetaIO read deadlocks#146
dzenanz merged 1 commit into
Kitware:masterfrom
blowekamp:fix-fflush-deadlock

Conversation

@blowekamp

Copy link
Copy Markdown
Collaborator

Summary

MetaObject::ReadStream() and MetaForm::ReadStream() unconditionally call fflush(nullptr) before parsing header fields. This line dates back to MetaIO's 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 thread's fflush(nullptr) call competes to lock every other thread's in-flight FILE* handle. In practice this can grind concurrent reads to a halt indefinitely.

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.

Ported from InsightSoftwareConsortium/ITK#6833.

Test plan

  • Configured a standalone CMake/Ninja build of this repo with -DBUILD_TESTING=ON
  • ninja build succeeds cleanly
  • ctest — all 14 tests pass, including testMeta2Object, testMeta3Image, and testMeta11Form which exercise the ReadStream() code paths touched by this change

…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>
@dzenanz
dzenanz merged commit 1755773 into Kitware:master Sep 4, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants