Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 614: Module & Finalizer: the module finalizer #709

Merged
merged 5 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(BaseSources
"RectGridIO.cpp"
"ParaGridIO.cpp"
"FileCallbackCloser.cpp"
"Finalizer.cpp"
"DevStep.cpp"
"StructureFactory.cpp"
"ModelArray.cpp"
Expand Down
2 changes: 2 additions & 0 deletions core/src/DevStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "include/DevStep.hpp"

#include "include/ConfiguredModule.hpp"
#include "include/Finalizer.hpp"
#include "include/IDiagnosticOutput.hpp"
#include "include/NextsimModule.hpp"
namespace Nextsim {
Expand All @@ -21,6 +22,7 @@ DevStep::DevStep()

void DevStep::init()
{
Finalizer::registerUnique(Module::finalize<IDiagnosticOutput>);
IDiagnosticOutput& ido = Module::getImplementation<IDiagnosticOutput>();
ido.setFilenamePrefix("diagnostic");
tryConfigure(ido);
Expand Down
61 changes: 61 additions & 0 deletions core/src/Finalizer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*!
* @file Finalizer.cpp
*
* @date Sep 11, 2024
* @author Tim Spain <[email protected]>
*/

#include "include/Finalizer.hpp"

namespace Nextsim {

void Finalizer::registerFunc(const FinalFn& fn)
{
auto& fns = functions();
fns.push_back(fn);
}

bool Finalizer::registerUnique(const FinalFn& fn)
{
if (contains(fn))
return false;
registerFunc(fn);
return true;
}

bool Finalizer::contains(const FinalFn& fn)
{
auto& fns = functions();
for (const auto& stored : fns) {
// Compare function addresses
if (*stored.target<FnType*>() == *fn.target<FnType*>())
return true;
}
return false;
}

void Finalizer::finalize()
{
while (!functions().empty()) {
try {
// Execute the last function in the list
functions().back()();
} catch (const std::exception& e) {
// Remove the function from the finalization list even if an exception was thrown.
functions().pop_back();
throw;
}
// Remove the function from the finalization list.
functions().pop_back();
}
}

size_t Finalizer::count() { return functions().size(); }

Finalizer::FinalFnContainer& Finalizer::functions()
{
static FinalFnContainer set;
return set;
}

}
19 changes: 7 additions & 12 deletions core/src/Model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "include/Configurator.hpp"
#include "include/ConfiguredModule.hpp"
#include "include/DevStep.hpp"
#include "include/Finalizer.hpp"
#include "include/IDiagnosticOutput.hpp"
#include "include/MissingData.hpp"
#include "include/NextsimModule.hpp"
Expand Down Expand Up @@ -58,17 +59,6 @@ Model::Model()

Model::~Model()
{
/*
* Try writing out a valid restart file. If the model and computer are in a
* state where this can be completed, great! If they are not then the
* restart file is unlikely to be valid or otherwise stored properly, and
* we abandon the writing.
*/
try {
writeRestartFile();
} catch (std::exception& e) {
// If there are any exceptions at all, fail without writing
}
}

void Model::configure()
Expand Down Expand Up @@ -174,7 +164,12 @@ Model::HelpMap& Model::getHelpRecursive(HelpMap& map, bool getAll)
return map;
}

void Model::run() { iterator.run(); }
void Model::run()
{
iterator.run();
writeRestartFile();
Finalizer::finalize();
}

//! Write a restart file for the model.
void Model::writeRestartFile()
Expand Down
6 changes: 6 additions & 0 deletions core/src/PrognosticData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "include/PrognosticData.hpp"

#include "include/Finalizer.hpp"
#include "include/ModelArrayRef.hpp"
#include "include/NextsimModule.hpp"
#include "include/gridNames.hpp"
Expand Down Expand Up @@ -35,6 +36,11 @@ PrognosticData::PrognosticData()

void PrognosticData::configure()
{
// Register finalizers before calling configure.
Finalizer::registerUnique(Module::finalize<IAtmosphereBoundary>);
Finalizer::registerUnique(Module::finalize<IOceanBoundary>);
Finalizer::registerUnique(Module::finalize<IDynamics>);

pAtmBdy = &Module::getImplementation<IAtmosphereBoundary>();
tryConfigure(pAtmBdy);

Expand Down
3 changes: 3 additions & 0 deletions core/src/StructureFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "include/StructureFactory.hpp"

#include "include/Finalizer.hpp"
#include "include/IStructure.hpp"
#include "include/NextsimModule.hpp"

Expand Down Expand Up @@ -52,6 +53,8 @@ ModelState StructureFactory::stateFromFile(const std::string& filePath, ModelMet
ModelState StructureFactory::stateFromFile(const std::string& filePath)
#endif
{
Finalizer::registerUnique(Module::finalize<IStructure>);

std::string structureName = structureNameFromFile(filePath);
// TODO There must be a better way
if (RectangularGrid::structureName == structureName) {
Expand Down
80 changes: 80 additions & 0 deletions core/src/include/Finalizer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*!
* @file ModelFinalizer.hpp
*
* @date Sep 3, 2024
* @author Tim Spain <[email protected]>
*/

#ifndef FINALIZER_HPP
#define FINALIZER_HPP

#include <functional>
#include <list>
#include <set>
#include <vector>

namespace Nextsim {

class Finalizer {
public:
using FnType = void();
using FinalFn = std::function<FnType>;
/*!
* Adds a function to be called at finalization. Functions are ordered last-in, first out.
* @param fn The function to be added. Must have void() signature.
*/
static void registerFunc(const FinalFn& fn);

/*!
* @brief Adds a function to be called at finalization unless it will already be called at
* finalization.
*
* @details Adds a function in the same way as atfinal(), unless the function already exists in
* the list of functions to be called at finalization. Identity of functions is equality of the
* values of their target<void()>() member functions. Functions are ordered last-in, first out.
* @param fn The function to be added. Must have void() signature.
* @return true if the function was added, false if it already existed and was not added.
*/
static bool registerUnique(const FinalFn& fn);

/*!
* @brief Returns whether a function will already be called at finalization.
*
* @details If the supplied function will already be called at finalization, return true, else
* return false. Identity of functions is equality of the values of their target<void()>()
* member functions.
* @param fn The function to be queried. Must have void() signature.
* @return true is the function is contained in those to be called at finalization, false if it
* is not.
*
*/
static bool contains(const FinalFn& fn);

/*!
* @brief Runs the finalize functions and erases them.
*
* @details In last-in, first-out order, the finalization functions are executed. If an
* exception is thrown, the throwing function will have been removed from the finalization
* functions, should execution be able to recover. If no exception is thrown, then there will
* be no finalization functions remaining. Otherwise, re-running finalize will begin execution
* with the function assigned previous to the one that threw the exception.
*/
static void finalize();

/*!
* Returns the number of finalize functions that will be run.
* @return the number of finalize functions that will be run.
*/
static size_t count();

private:
typedef std::list<FinalFn> FinalFnContainer;
/*!
* Returns a reference to a static container of the finalizer functions.
*/
static FinalFnContainer& functions();
};

} /* namespace Nextsim */

#endif /* FINALIZER_HPP */
1 change: 1 addition & 0 deletions core/src/include/Module.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ template <typename I> I& getImplementation();
template <typename I> void setImplementation(const std::string& impl);
template <typename I> HelpMap& getHelpRecursive(HelpMap& map, bool getAll);
template <typename I> std::string implementation();
template <typename I> void finalize();

}
#endif /* MODULE_HPP */
7 changes: 7 additions & 0 deletions core/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ else()
)
target_link_libraries(testFileCallbackCloser PRIVATE doctest::doctest)

add_executable(
testFinalizer
"Finalizer_test.cpp"
"../../core/src/Finalizer.cpp"
)
target_link_libraries(testFinalizer PRIVATE doctest::doctest)

set(MODEL_INCLUDE_DIR "./testmodelarraydetails")

add_executable(
Expand Down
119 changes: 119 additions & 0 deletions core/test/Finalizer_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*!
* @file ModelFinalizer_test.cpp
*
* @date Sep 3, 2024
* @author Tim Spain <[email protected]>
*/

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

#include "include/Finalizer.hpp"

namespace Nextsim {

// A mock Model class. Does nothing except call copy the behaviour of the real
// Model class in its destructor
class Model
{
public:
Model() = default;
~Model() { Finalizer::finalize(); }
};

class TheCount
{
public:
inline static size_t increment() { return get()++; }
inline static size_t preincrement() { return ++get(); }
inline static void incrementAndPrint() { std::cout << "TheCount=" << preincrement() << std::endl; }
inline static size_t value() { return get(); }
inline static size_t count() { return get(); }
inline static void reset() { get() = 0; }
protected:
inline static size_t& get()
{
static size_t count;
return count;
}
};

// In the British aristocracy an Earl is equivalent to a count in the rest of Europe!
class Earl
{
public:
inline static void incr() { TheCount::increment(); }
};

class HappyTime : public std::exception
{
public:
const char* what() const noexcept { return "Nothing is wrong :D"; }
};

void throwHappy() { throw HappyTime(); }

TEST_SUITE_BEGIN("ModelFinalizer");
TEST_CASE("Duplicate functions")
{
TheCount::reset();
REQUIRE(TheCount::count() == 0);
// Finalizer::clear();
REQUIRE(Finalizer::count() == 0);
Finalizer::registerFunc(Earl::incr);
Finalizer::registerFunc(Earl::incr);
REQUIRE(Finalizer::count() == 2);
Finalizer::finalize();
REQUIRE(TheCount::count() == 2);
}

TEST_CASE("Unique functions")
{
TheCount::reset();
REQUIRE(TheCount::count() == 0);
// Finalizer::clear();
REQUIRE(Finalizer::count() == 0);
Finalizer::registerUnique(Earl::incr);
REQUIRE(Finalizer::contains(Earl::incr));
Finalizer::registerUnique(Earl::incr);
REQUIRE(Finalizer::count() == 1);
Finalizer::finalize();
REQUIRE(TheCount::count() == 1);
}

TEST_CASE("Finalize")
{
TheCount::reset();
REQUIRE(TheCount::count() == 0);
// Finalizer::clear();
REQUIRE(Finalizer::count() == 0);
Finalizer::registerFunc(Earl::incr);
Finalizer::registerFunc(Earl::incr);
REQUIRE(Finalizer::count() == 2);
Finalizer::finalize();
REQUIRE(Finalizer::count() == 0);
REQUIRE(TheCount::count() == 2);
TheCount::reset();
Finalizer::finalize();
REQUIRE(Finalizer::count() == 0);
REQUIRE(TheCount::count() == 0);
}

TEST_CASE("Exception")
{
TheCount::reset();
REQUIRE(TheCount::count() == 0);
// Finalizer::clear();
REQUIRE(Finalizer::count() == 0);
Finalizer::registerFunc(Earl::incr);
Finalizer::registerFunc(throwHappy);
Finalizer::registerFunc(Earl::incr);
REQUIRE(Finalizer::count() == 3);
REQUIRE_THROWS_AS(Finalizer::finalize(), HappyTime);
// Only one increment occurred because of the exception
REQUIRE(TheCount::count() == 1);
// The executed increment and throwing function have been removed. Only one function (the
// second increment) should remain.
REQUIRE(Finalizer::count() == 1);
}
} /* namespace Nextsim */
Loading