namespace gmxapi
{
-MpiContextManager::MpiContextManager()
+MpiContextManager::MpiContextManager() :
+ communicator_(std::make_unique<MPI_Comm>(GMX_LIB_MPI ? MPI_COMM_WORLD : MPI_COMM_NULL))
{
+ // Safely increments the GROMACS MPI initialization counter after checking
+ // whether the MPI library is already initialized. After this call, MPI_Init
+ // or MPI_Init_thread has been called exactly once.
gmx::init(nullptr, nullptr);
GMX_RELEASE_ASSERT(!GMX_LIB_MPI || gmx_mpi_initialized(),
"MPI should be initialized before reaching this point.");
+ if (this->communicator() != MPI_COMM_NULL)
+ {
+ // Synchronise at the point of acquiring a MpiContextManager.
+ gmx_barrier(this->communicator());
+ }
};
MpiContextManager::~MpiContextManager()
{
- // This is always safe to call. It is a no-op if
- // thread-MPI, and if the constructor completed then the
- // MPI library is initialized with reference counting.
- gmx::finalize();
+ if (communicator_)
+ {
+ // This is always safe to call. It is a no-op if
+ // thread-MPI, and if the constructor completed then the
+ // MPI library is initialized with reference counting.
+ gmx::finalize();
+ }
+}
+
+MPI_Comm MpiContextManager::communicator() const
+{
+ if (!communicator_)
+ {
+ throw UsageError("Invalid MpiContextManager. Accessed after `move`?");
+ }
+ return *communicator_;
}
ContextImpl::~ContextImpl() = default;
Context createContext()
{
MpiContextManager contextmanager;
- auto impl = ContextImpl::create();
+ auto impl = ContextImpl::create(std::move(contextmanager));
GMX_ASSERT(impl, "ContextImpl creation method should not be able to return null.");
auto context = Context(impl);
return context;
}
-ContextImpl::ContextImpl() noexcept(std::is_nothrow_constructible_v<gmx::LegacyMdrunOptions>) :
- mpi_(MpiContextManager())
+ContextImpl::ContextImpl(MpiContextManager&& mpi) noexcept(std::is_nothrow_constructible_v<gmx::LegacyMdrunOptions>) :
+ mpi_(std::move(mpi))
{
+ // Confirm our understanding of the MpiContextManager invariant.
+ GMX_ASSERT(mpi_.communicator() == MPI_COMM_NULL ? !GMX_LIB_MPI : GMX_LIB_MPI,
+ "Precondition is an appropriate communicator for the library environment.");
// Make sure we didn't change the data members and overlook implementation details.
GMX_ASSERT(session_.expired(),
"This implementation assumes an expired weak_ptr at initialization.");
}
-std::shared_ptr<ContextImpl> ContextImpl::create()
+std::shared_ptr<ContextImpl> ContextImpl::create(MpiContextManager&& mpi)
{
std::shared_ptr<ContextImpl> impl;
- impl.reset(new ContextImpl());
+ impl.reset(new ContextImpl(std::move(mpi)));
return impl;
}
// lifetime. The gmx wrapper binary uses the same infrastructure,
// but the lifetime is now trivially that of the invocation of the
// wrapper binary.
- auto communicator = GMX_LIB_MPI ? MPI_COMM_WORLD : MPI_COMM_NULL;
+ auto communicator = mpi_.communicator();
+ // Confirm the precondition for simulationContext().
+ GMX_ASSERT(communicator == MPI_COMM_NULL ? !GMX_LIB_MPI : GMX_LIB_MPI,
+ "Context communicator does not have an appropriate value for the environment.");
SimulationContext simulationContext(communicator, multiSimDirectoryNames);
}
// As of gmxapi 0.0.3 there is only one Context type
-Context::Context() : Context{ ContextImpl::create() }
+Context::Context() : Context{ ContextImpl::create(MpiContextManager()) }
{
GMX_ASSERT(impl_, "Context requires a non-null implementation member.");
}
#include "gromacs/mdrun/legacymdrunoptions.h"
#include "gromacs/mdtypes/mdrunoptions.h"
+#include "gromacs/utility/gmxmpi.h"
#include "gmxapi/context.h"
#include "gmxapi/session.h"
{
/*!
- * \brief Provide RAII management of communications resource state.
+ * \brief Provide RAII management of library MPI context.
*
* To acquire an MpiContextManager is to have assurance that any external MPI
* environment is ready to use. When the MpiContextManager is released or
* goes out of scope, the destructor finalizes the resources.
*
+ * It is plausible that one or more ranks may not participate in an API session,
+ * but we have not historically handled such use cases at this level.
+ * For GROMACS built with an MPI library, the root communicator in the MpiContextManager
+ * must be valid. For other builds (e.g. tMPI), the communicator member must be MPI_COMM_NULL.
+ *
+ * If the client provides a communicator, the client promises to maintain the validity
+ * of the communicator for the life of the MpiContextManager.
+ *
* Note that thread-MPI chooses the number of ranks and constructs its
* MPI communicator internally, so does not and is unlikely to ever
* participate here.
*
- * \todo There is no resource for logging or reporting errors during initialization
+ * \todo Log errors during set up or tear down.
+ * There is no resource for logging or reporting errors during initialization or when misused.
*
* \ingroup gmxapi
*/
class MpiContextManager
{
public:
+ /*!
+ * \brief Default constructor.
+ *
+ * Construct a valid instance with an appropriate default value for the
+ * base communicator. (Note that appropriate default value depends on whether
+ * the library was compiled with an external MPI library.)
+ */
MpiContextManager();
~MpiContextManager();
//! \}
/*!
- * \brief Move semantics are non-trivial.
+ * \brief Transfer ownership of the managed GROMACS MPI context.
*
* \{
*/
- MpiContextManager(MpiContextManager&&) noexcept = delete;
- MpiContextManager& operator=(MpiContextManager&&) noexcept = delete;
+ MpiContextManager(MpiContextManager&& source) noexcept = default;
+ MpiContextManager& operator=(MpiContextManager&& source) noexcept = default;
//! \}
+
+ /*!
+ * \brief Get the communicator for this context.
+ *
+ * \return Communicator with an appropriate initialized state for the current library
+ * configuration.
+ *
+ * \throws gmxapi::UsageError if MpiContextManager is in an invalid state, such as after
+ * being moved from.
+ */
+ [[nodiscard]] MPI_Comm communicator() const;
+
+private:
+ /*!
+ * \brief The resources provided by this manager.
+ *
+ * A valid MpiContextManager has a value for communicator_ that is appropriate
+ * for the library configuration.
+ */
+ std::unique_ptr<MPI_Comm> communicator_;
};
/*!
*
* \return ownership of a new object
*/
- static std::shared_ptr<ContextImpl> create();
+ static std::shared_ptr<ContextImpl> create(MpiContextManager&& mpi);
/*!
* \brief Copy disallowed because Session state would become ambiguous.
* Part of the ContextImpl invariant establishes a point where MPI initialization status is
* known.
*
+ * Note that the MpiContextManager invariant is dependent on configure-time options to the
+ * GROMACS library. Specifically, MpiContextManager::communicator() is guaranteed to be
+ * MPI_COMM_NULL if and only if the library was built without an external MPI library.
+ * This is confusing and we should split either this class or the
+ * MpiContextManager class. Another alternative would be to use a std::optional here and to
+ * allow the std::unique_ptr<MpiContextManager> provided to ContextImpl::create() to be null,
+ * but that means a slight change of documented behavior protocol. See #3688 and #3650 for
+ * follow up.
+ *
* To ensure the MpiContextManager is initialized only once, we use a const member that must
* be initialized at construction.
*/
* Don't use this. Use create() to get a shared pointer right away.
* Otherwise, shared_from_this() is potentially dangerous.
*/
- explicit ContextImpl() noexcept(std::is_nothrow_constructible_v<gmx::LegacyMdrunOptions>);
+ explicit ContextImpl(MpiContextManager&& mpi) noexcept(std::is_nothrow_constructible_v<gmx::LegacyMdrunOptions>);
};