diff --git a/opm/simulators/flow/Main.hpp b/opm/simulators/flow/Main.hpp index e2a8b8fc81..d44dae0369 100644 --- a/opm/simulators/flow/Main.hpp +++ b/opm/simulators/flow/Main.hpp @@ -319,10 +319,26 @@ protected: Opm::compileTimestamp()); setupTime_ = externalSetupTimer.elapsed(); } - catch (const std::invalid_argument& e) + // readDeck() may throw std::runtime_error on parse failure. + // These exceptions are synchronized across MPI ranks via comm.min() + // inside readDeck(), so cooperative shutdown is safe — returning false + // lets Main::~Main() call MPI_Finalize(). + catch (const std::runtime_error& e) { if (outputCout_) { std::cerr << "Failed to create valid EclipseState object." << std::endl; + std::cerr << e.what() << std::endl; + } + exitCode = EXIT_FAILURE; + return false; + } + // Other exceptions (e.g., std::bad_alloc, std::invalid_argument) + // may not be synchronized across ranks. Use MPI_Abort() to prevent + // deadlocks from unsynchronized failures. + catch (const std::exception& e) + { + if (outputCout_) { + std::cerr << "Unexpected error during initialization." << std::endl; std::cerr << "Exception caught: " << e.what() << std::endl; } #if HAVE_MPI diff --git a/opm/simulators/utils/readDeck.cpp b/opm/simulators/utils/readDeck.cpp index 33a5b997e0..345c917648 100644 --- a/opm/simulators/utils/readDeck.cpp +++ b/opm/simulators/utils/readDeck.cpp @@ -780,6 +780,10 @@ void Opm::readDeck(Opm::Parallel::Communication comm, errorGuard->clear(); } + // Synchronize parse status across all ranks. This serves two purposes: + // 1. Propagate rank 0 failure to all ranks (parsing only happens on rank 0) + // 2. Ensure all ranks throw together, enabling cooperative shutdown + // via Main::~Main() destructor calling MPI_Finalize() parseSuccess = comm.min(parseSuccess); if (! parseSuccess) { @@ -787,11 +791,11 @@ void Opm::readDeck(Opm::Parallel::Communication comm, OpmLog::error(fmt::format("Unrecoverable errors while loading input:\n{}", failureMessage)); } -#if HAVE_MPI - MPI_Finalize(); -#endif - - std::exit(EXIT_FAILURE); + // Throw instead of std::exit() so that callers can handle the + // error. In production, Main::initialize_() catches this and + // returns false for cooperative shutdown. In unit tests, + // Boost.Test catches it and reports a proper test failure. + throw std::runtime_error(failureMessage); } }