From fc800aea5f27d591e7120399c3a5a22a3af6c616 Mon Sep 17 00:00:00 2001 From: Mark Olesen <Mark.Olesen@esi-group.com> Date: Wed, 5 Aug 2020 17:16:54 +0200 Subject: [PATCH] ENH: finer granularity for handling functionObject failure (#1779) - additional "errors" entry with enumerated values (default|warn|ignore|strict) for defining warning or error at construct or runtime stage - default : construct = warn, runtime = fatal - warn : construct = warn, runtime = warn - ignore : construct = silent, runtime = silent - strict : construct = fatal, runtime = fatal The errors control can be added at the top-level and/or for individual function objects. --- .../functionObject/functionObject.H | 18 +- .../functionObjectList/functionObjectList.C | 479 ++++++++++++++---- .../functionObjectList/functionObjectList.H | 84 ++- 3 files changed, 480 insertions(+), 101 deletions(-) diff --git a/src/OpenFOAM/db/functionObjects/functionObject/functionObject.H b/src/OpenFOAM/db/functionObjects/functionObject/functionObject.H index dd43486501a..50b90f75d3b 100644 --- a/src/OpenFOAM/db/functionObjects/functionObject/functionObject.H +++ b/src/OpenFOAM/db/functionObjects/functionObject/functionObject.H @@ -56,13 +56,15 @@ Description sub-dictionary, typically as in the following example: \verbatim - functions // sub-dictionary name under the system/controlDict file + functions // sub-dictionary name under the system/controlDict file { - <userDefinedSubDictName1> + ..optional entries.. + + <dictName1> { // Mandatory entries - type <functionObjectTypeName>; - libs (<libType>FunctionObjects); + type <functionObjectTypeName>; + libs (<libType>FunctionObjects); // Mandatory entries defined in <functionObjectType> ... @@ -82,14 +84,14 @@ Description writeInterval 1; } - <userDefinedSubDictName2> + <dictName2> { ... } ... - <userDefinedSubDictNameN> + <dictNameN> { ... } @@ -101,6 +103,7 @@ Description Property | Description | Type | Reqd | Deflt type | Type name of function object | word | yes | - libs | Library name(s) for implementation | words | no | - + errors | Error handling (default/warn/ignore/strict) | word | no | inherits region | Name of region for multi-region cases | word | no | region0 enabled | Switch to turn function object on/off | bool | no | true log | Switch to write log info to standard output | bool | no | true @@ -112,6 +115,9 @@ Description writeInterval | Steps/time between write phases | label | no | 1 \endtable + If the \c errors entry is missing, it uses the value (if any) + specified within the top-level functionObjectList. + Time controls: \table Option | Description diff --git a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C index fc54c45ff1e..f7f465065c5 100644 --- a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C +++ b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C @@ -37,15 +37,64 @@ License #include "Tuple2.H" #include "etcFiles.H" #include "IOdictionary.H" +#include "Pstream.H" +#include "OSspecific.H" /* * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * * */ +//- Max number of warnings (per functionObject) +static constexpr const uint32_t maxWarnings = 10u; + Foam::fileName Foam::functionObjectList::functionObjectDictPath ( "caseDicts/postProcessing" ); +const Foam::Enum +< + Foam::functionObjectList::errorHandlingType +> +Foam::functionObjectList::errorHandlingNames_ +({ + { errorHandlingType::DEFAULT, "default" }, + { errorHandlingType::WARN, "warn" }, + { errorHandlingType::IGNORE, "ignore" }, + { errorHandlingType::STRICT, "strict" }, +}); + + +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace Foam +{ + //- Mimic exit handling of the error class + static void exitNow(const error& err) + { + if (hasEnv("FOAM_ABORT")) + { + Perr<< nl << err << nl + << "\nFOAM aborting (FOAM_ABORT set)\n" << endl; + error::printStack(Perr); + std::abort(); + } + else if (Pstream::parRun()) + { + Perr<< nl << err << nl + << "\nFOAM parallel run exiting\n" << endl; + Pstream::exit(1); + } + else + { + Perr<< nl << err << nl + << "\nFOAM exiting\n" << endl; + std::exit(1); + } + } + +} // End namespace Foam + + // * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * // void Foam::functionObjectList::createStateDict() const @@ -282,7 +331,7 @@ bool Foam::functionObjectList::readFunctionObject // Search for the functionObject dictionary fileName path = functionObjectList::findDict(funcName); - if (path == fileName::null) + if (path.empty()) { WarningInFunction << "Cannot find functionObject file " << funcName << endl; @@ -291,7 +340,7 @@ bool Foam::functionObjectList::readFunctionObject // Read the functionObject dictionary autoPtr<ISstream> fileStreamPtr(fileHandler().NewIFstream(path)); - ISstream& fileStream = fileStreamPtr(); + ISstream& fileStream = *fileStreamPtr; dictionary funcsDict(fileStream); dictionary* funcDictPtr = funcsDict.findDict(funcName); @@ -347,6 +396,45 @@ bool Foam::functionObjectList::readFunctionObject } +Foam::functionObjectList::errorHandlingType +Foam::functionObjectList::getOrDefaultErrorHandling +( + const word& key, + const dictionary& dict, + const errorHandlingType deflt +) const +{ + const entry* eptr = dict.findEntry(key, keyType::LITERAL); + + if (eptr) + { + if (eptr->isDict()) + { + Warning + << "The sub-dictionary '" << key + << "' masks error handling for functions" << endl; + } + else + { + const word enumName(eptr->get<word>()); + + if (!errorHandlingNames_.found(enumName)) + { + // Failed the name lookup + FatalIOErrorInFunction(dict) + << enumName << " is not in enumeration: " + << errorHandlingNames_ << nl + << exit(FatalIOError); + } + + return errorHandlingNames_.get(enumName); + } + } + + return deflt; +} + + // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::functionObjectList::functionObjectList @@ -355,15 +443,7 @@ Foam::functionObjectList::functionObjectList const bool execution ) : - PtrList<functionObject>(), - digests_(), - indices_(), - time_(runTime), - parentDict_(runTime.controlDict()), - stateDictPtr_(), - objectsRegistryPtr_(), - execution_(execution), - updated_(false) + functionObjectList(runTime, runTime.controlDict(), execution) {} @@ -375,12 +455,14 @@ Foam::functionObjectList::functionObjectList ) : PtrList<functionObject>(), + errorHandling_(), digests_(), indices_(), + warnings_(), time_(runTime), parentDict_(parentDict), - stateDictPtr_(), - objectsRegistryPtr_(), + stateDictPtr_(nullptr), + objectsRegistryPtr_(nullptr), execution_(execution), updated_(false) {} @@ -442,9 +524,7 @@ Foam::autoPtr<Foam::functionObjectList> Foam::functionObjectList::New { modifiedControlDict = true; - wordList funcNames = args.getList<word>("funcs"); - - for (const word& funcName : funcNames) + for (const word& funcName : args.getList<word>("funcs")) { readFunctionObject ( @@ -478,17 +558,14 @@ Foam::autoPtr<Foam::functionObjectList> Foam::functionObjectList::New Foam::label Foam::functionObjectList::triggerIndex() const { - label triggeri = labelMin; - stateDict().readIfPresent("triggerIndex", triggeri); - - return triggeri; + return stateDict().getOrDefault<label>("triggerIndex", labelMin); } void Foam::functionObjectList::resetState() { // Reset (re-read) the state dictionary - stateDictPtr_.clear(); + stateDictPtr_.reset(nullptr); createStateDict(); } @@ -540,19 +617,21 @@ const Foam::objectRegistry& Foam::functionObjectList::storedObjects() const void Foam::functionObjectList::clear() { PtrList<functionObject>::clear(); + errorHandling_.clear(); digests_.clear(); indices_.clear(); + warnings_.clear(); updated_ = false; } -Foam::label Foam::functionObjectList::findObjectID(const word& name) const +Foam::label Foam::functionObjectList::findObjectID(const word& objName) const { label id = 0; for (const functionObject& funcObj : functions()) { - if (funcObj.name() == name) + if (funcObj.name() == objName) { return id; } @@ -600,19 +679,140 @@ bool Foam::functionObjectList::execute() read(); } + auto errIter = errorHandling_.cbegin(); + for (functionObject& funcObj : functions()) { + const errorHandlingType errorHandling = *errIter; + ++errIter; + const word& objName = funcObj.name(); + + if + ( + errorHandling == errorHandlingType::WARN + || errorHandling == errorHandlingType::IGNORE + ) { - addProfiling(fo, "functionObject::" + objName + "::execute"); + // Throw FatalError, FatalIOError as exceptions - ok = funcObj.execute() && ok; - } + const bool throwingError = FatalError.throwExceptions(); + const bool throwingIOerr = FatalIOError.throwExceptions(); + + bool hadError = false; + + // execute() + try + { + addProfiling + ( + fo, + "functionObject::" + objName + "::execute" + ); + + ok = funcObj.execute() && ok; + } + catch (const Foam::error& err) + { + // Treat IOerror and error identically + uint32_t nWarnings; + hadError = true; + + if + ( + errorHandling != errorHandlingType::IGNORE + && (nWarnings = ++warnings_(objName)) <= maxWarnings + ) + { + // Trickery to get original message + err.write(Warning, false); + Info<< nl + << "--> execute() function object '" + << objName << "'"; + + if (nWarnings == maxWarnings) + { + Info<< nl << "... silencing further warnings"; + } + + Info<< nl << endl; + } + } + + if (hadError) + { + // Restore previous state + FatalError.throwExceptions(throwingError); + FatalIOError.throwExceptions(throwingIOerr); + continue; + } + + // write() + try + { + addProfiling + ( + fo, + "functionObject::" + objName + ":write" + ); + + ok = funcObj.write() && ok; + } + catch (const Foam::error& err) + { + // Treat IOerror and error identically + uint32_t nWarnings; + + if + ( + errorHandling != errorHandlingType::IGNORE + && (nWarnings = ++warnings_(objName)) <= maxWarnings + ) + { + // Trickery to get original message + err.write(Warning, false); + Info<< nl + << "--> write() function object '" + << objName << "'"; + + if (nWarnings == maxWarnings) + { + Info<< nl << "... silencing further warnings"; + } + + Info<< nl << endl; + } + } + // Restore previous state + FatalError.throwExceptions(throwingError); + FatalIOError.throwExceptions(throwingIOerr); + } + else { - addProfiling(fo, "functionObject::" + objName + "::write"); + // No special trapping of errors + + // execute() + { + addProfiling + ( + fo, + "functionObject::" + objName + "::execute" + ); - ok = funcObj.write() && ok; + ok = funcObj.execute() && ok; + } + + // write() + { + addProfiling + ( + fo, + "functionObject::" + objName + ":write" + ); + + ok = funcObj.write() && ok; + } } } } @@ -620,7 +820,7 @@ bool Foam::functionObjectList::execute() // Force writing of state dictionary after function object execution if (time_.writeTime()) { - label oldPrecision = IOstream::precision_; + const auto oldPrecision = IOstream::precision_; IOstream::precision_ = 16; stateDictPtr_->writeObject @@ -644,6 +844,8 @@ bool Foam::functionObjectList::execute(const label subIndex) { for (functionObject& funcObj : functions()) { + // Probably do not need try/catch... + ok = funcObj.execute(subIndex) && ok; } } @@ -666,6 +868,8 @@ bool Foam::functionObjectList::execute { if (stringOps::match(functionNames, funcObj.name())) { + // Probably do not need try/catch... + ok = funcObj.execute(subIndex) && ok; } } @@ -686,13 +890,55 @@ bool Foam::functionObjectList::end() read(); } + auto errIter = errorHandling_.cbegin(); + for (functionObject& funcObj : functions()) { + const errorHandlingType errorHandling = *errIter; + ++errIter; + const word& objName = funcObj.name(); - addProfiling(fo, "functionObject::" + objName + "::end"); + // Ignore failure on end() - not much we can do anyhow + + // Throw FatalError, FatalIOError as exceptions + const bool throwingError = FatalError.throwExceptions(); + const bool throwingIOerr = FatalIOError.throwExceptions(); + + try + { + addProfiling(fo, "functionObject::" + objName + "::end"); + ok = funcObj.end() && ok; + } + catch (const Foam::error& err) + { + // Treat IOerror and error identically + uint32_t nWarnings; + + if + ( + errorHandling != errorHandlingType::IGNORE + && (nWarnings = ++warnings_(objName)) <= maxWarnings + ) + { + // Trickery to get original message + err.write(Warning, false); + Info<< nl + << "--> end() function object '" + << objName << "'"; + + if (nWarnings == maxWarnings) + { + Info<< nl << "... silencing further warnings"; + } + + Info<< nl << endl; + } + } - ok = funcObj.end() && ok; + // Restore previous state + FatalError.throwExceptions(throwingError); + FatalIOError.throwExceptions(throwingIOerr); } } @@ -715,7 +961,13 @@ bool Foam::functionObjectList::adjustTimeStep() { const word& objName = funcObj.name(); - addProfiling(fo, "functionObject::" + objName + "::adjustTimeStep"); + // Probably do not need try/catch... + + addProfiling + ( + fo, + "functionObject::" + objName + "::adjustTimeStep" + ); ok = funcObj.adjustTimeStep() && ok; } @@ -750,8 +1002,10 @@ bool Foam::functionObjectList::read() { // No functions PtrList<functionObject>::clear(); + errorHandling_.clear(); digests_.clear(); indices_.clear(); + warnings_.clear(); } else if (!entryPtr->isDict()) { @@ -767,10 +1021,18 @@ bool Foam::functionObjectList::read() PtrList<functionObject> newPtrs(functionsDict.size()); List<SHA1Digest> newDigs(functionsDict.size()); + + errorHandling_.resize + ( + functionsDict.size(), + errorHandlingType::DEFAULT + ); + HashTable<label> newIndices; addProfiling(fo, "functionObjects::read"); + // Top-level "libs" specification (optional) time_.libs().open ( functionsDict, @@ -778,6 +1040,15 @@ bool Foam::functionObjectList::read() functionObject::dictionaryConstructorTablePtr_ ); + // Top-level "errors" specification (optional) + const errorHandlingType errorHandlingFallback = + getOrDefaultErrorHandling + ( + "errors", + functionsDict, + errorHandlingType::DEFAULT + ); + label nFunc = 0; for (const entry& dEntry : functionsDict) @@ -786,7 +1057,7 @@ bool Foam::functionObjectList::read() if (!dEntry.isDict()) { - if (key != "libs") + if (key != "errors" && key != "libs") { IOWarningInFunction(parentDict_) << "Entry " << key << " is not a dictionary" << endl; @@ -799,66 +1070,67 @@ bool Foam::functionObjectList::read() bool enabled = dict.getOrDefault("enabled", true); + // Per-function "errors" specification + const errorHandlingType errorHandling = + getOrDefaultErrorHandling + ( + "errors", + dict, + errorHandlingFallback + ); + + errorHandling_[nFunc] = errorHandling; + newDigs[nFunc] = dict.digest(); label oldIndex = -1; autoPtr<functionObject> objPtr = remove(key, oldIndex); + const bool needsTimeControl = + functionObjects::timeControl::entriesPresent(dict); + if (objPtr) { - // Re-read if dictionary content changed for - // existing functionObject + // Existing functionObject: + // Re-read if dictionary content changed and did not + // change timeControl <-> regular + if (enabled && newDigs[nFunc] != digests_[oldIndex]) { - addProfiling - ( - fo2, - "functionObject::" + objPtr->name() + "::read" - ); + const bool wasTimeControl = + isA<functionObjects::timeControl>(*objPtr); - if (functionObjects::timeControl::entriesPresent(dict)) + if (needsTimeControl != wasTimeControl) { - if (isA<functionObjects::timeControl>(objPtr())) - { - // Already a time control - normal read - enabled = objPtr->read(dict); - } - else - { - // Was not a time control - need to re-create - objPtr.reset - ( - new functionObjects::timeControl - ( - key, - time_, - dict - ) - ); - - enabled = true; - } + // Changed from timeControl <-> regular + + // Fallthrough to 'new' + objPtr.reset(nullptr); } else { - // Plain function object - normal read + // Normal read. Assume no errors to trap + + addProfiling + ( + fo, + "functionObject::" + objPtr->name() + "::read" + ); + enabled = objPtr->read(dict); } - - ok = enabled && ok; } if (!enabled) { // Delete disabled or an invalid(read) functionObject - objPtr.clear(); + objPtr.reset(nullptr); continue; } } - else if (enabled) - { - autoPtr<functionObject> foPtr; + if (enabled && !objPtr) + { // Throw FatalError, FatalIOError as exceptions const bool throwingError = FatalError.throwExceptions(); const bool throwingIOerr = FatalIOError.throwExceptions(); @@ -868,47 +1140,72 @@ bool Foam::functionObjectList::read() // New functionObject addProfiling ( - fo2, + fo, "functionObject::" + key + "::new" ); - if (functionObjects::timeControl::entriesPresent(dict)) + if (needsTimeControl) { - foPtr.reset + objPtr.reset ( new functionObjects::timeControl(key, time_, dict) ); } else { - foPtr = functionObject::New(key, time_, dict); + objPtr = functionObject::New(key, time_, dict); } } - catch (const Foam::IOerror& ioErr) - { - Info<< ioErr << nl << endl; - std::exit(1); - } catch (const Foam::error& err) { - // Bit of trickery to get the original message - err.write(Warning, false); - InfoInFunction - << nl - << "--> while loading function object '" << key << "'" - << nl << endl; + objPtr.reset(nullptr); // extra safety + + switch (errorHandling) + { + case errorHandlingType::IGNORE: + break; + + case errorHandlingType::STRICT: + { + exitNow(err); + break; + } + + case errorHandlingType::DEFAULT: + { + if (isA<Foam::IOerror>(err)) + { + // Fatal on Foam::IOerror + exitNow(err); + break; + } + + // Warn on Foam::error + #if (__cplusplus >= 201703L) + [[fallthrough]]; + #endif + } + + case errorHandlingType::WARN: + { + // Trickery to get original message + err.write(Warning, false); + Info<< nl + << "--> loading function object '" + << key << "'" + << nl << endl; + break; + } + } } - // Restore previous exception throwing state + // Restore previous state FatalError.throwExceptions(throwingError); FatalIOError.throwExceptions(throwingIOerr); - // Required functionObject to be valid on all processors - if (returnReduce(bool(foPtr), andOp<bool>())) - { - objPtr.reset(foPtr.release()); - } - else + // Require functionObject to be valid on all processors + if (!returnReduce(bool(objPtr), andOp<bool>())) { + objPtr.reset(nullptr); ok = false; } } @@ -924,12 +1221,14 @@ bool Foam::functionObjectList::read() newPtrs.resize(nFunc); newDigs.resize(nFunc); + errorHandling_.resize(nFunc); // Updating PtrList of functionObjects deletes any // existing unused functionObjects PtrList<functionObject>::transfer(newPtrs); digests_.transfer(newDigs); indices_.transfer(newIndices); + warnings_.clear(); } return ok; diff --git a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.H b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.H index bc1801e18e9..b96c665693e 100644 --- a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.H +++ b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.H @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2016 OpenFOAM Foundation - Copyright (C) 2015-2019 OpenCFD Ltd. + Copyright (C) 2015-2020 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -31,6 +31,43 @@ Description List of function objects with start(), execute() and end() functions that is called for each object. + \verbatim + functions // sub-dictionary name under the system/controlDict file + { + ..optional entries.. + + <userDict1> + { + // Mandatory entries + type <typeName>; + libs (<libName> .. <libName>); + ... + } + + <userDict2> + { + ... + } + + ... + } + \endverbatim + + with optional entries: + \table + Property | Description | Type | Reqd | Deflt + libs | Preloaded library names | words | no | - + errors | Error handling (default/warn/ignore/strict) | word | no | inherits + \endtable + + The optional \c errors entry controls how FatalError is caught + during construction and execute/write. FatalIOError is unaffected. + - \c default : warn on construction errors, fatal on runtime errors + - \c warn : warn on construction and runtime errors + - \c ignore : ignore construction and runtime errors + - \c strict : fatal on construction and runtime errors + . + See also Foam::functionObject Foam::functionObjects::timeControl @@ -44,6 +81,7 @@ SourceFiles #define functionObjectList_H #include "PtrList.H" +#include "Enum.H" #include "functionObject.H" #include "SHA1Digest.H" #include "HashTable.H" @@ -55,7 +93,7 @@ SourceFiles namespace Foam { -// Forward declarations +// Forward Declarations class argList; class mapPolyMesh; class wordRe; @@ -68,14 +106,37 @@ class functionObjectList : private PtrList<functionObject> { - // Private data + // Data Types + + //- Handling of construction or execution errors + enum class errorHandlingType : uint8_t + { + DEFAULT = 0, //!< Warn on construct, Fatal on runtime + WARN, //!< Warn on construct, Warn on runtime + IGNORE, //!< Ignore on construct, Ignore on runtime + STRICT, //!< Fatal on construct, Fatal on runtime + }; + + //- Names for error handling types + static const Enum<errorHandlingType> errorHandlingNames_; + + + // Private Data + + //- A list of error/warning handling + List<errorHandlingType> errorHandling_; //- A list of SHA1 digests for the function object dictionaries List<SHA1Digest> digests_; - //- Quick lookup of the index into functions/digests + //- Quick lookup of the index into functions/digests/errorHandling HashTable<label> indices_; + //- Track the number of warnings per function object and limit + // to a predefined number to avoid flooding the display. + // Clear on re-read of functions. + HashTable<uint32_t> warnings_; + //- Reference to Time const Time& time_; @@ -119,6 +180,19 @@ class functionObjectList //- configuration files, add to the given map and recurse static void listDir(const fileName& dir, wordHashSet& available); + //- Like Enum::getOrDefault, but with additional code to warn if + //- the 'key' is not a primitive entry. + // + // This additional treatment is to ensure that potentially existing + // code with an "errors" functionObject will continue to run. + errorHandlingType getOrDefaultErrorHandling + ( + const word& key, + const dictionary& dict, + const errorHandlingType deflt + ) const; + + //- No copy construct functionObjectList(const functionObjectList&) = delete; @@ -214,7 +288,7 @@ public: void clear(); //- Find the ID of a given function object by name, -1 if not found. - label findObjectID(const word& name) const; + label findObjectID(const word& objName) const; //- Print a list of functionObject configuration files in the //- directories located using -- GitLab