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.
This commit is contained in:
Mark Olesen
2020-08-05 17:16:54 +02:00
parent f39c1d3c57
commit 5424c5e5bc
3 changed files with 478 additions and 101 deletions

View File

@ -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

View File

@ -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();
{
addProfiling(fo, "functionObject::" + objName + "::execute");
ok = funcObj.execute() && ok;
if
(
errorHandling == errorHandlingType::WARN
|| errorHandling == errorHandlingType::IGNORE
)
{
// Throw FatalError, FatalIOError as exceptions
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
ok = funcObj.write() && ok;
// execute()
{
addProfiling
(
fo,
"functionObject::" + objName + "::execute"
);
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
ok = funcObj.end() && ok;
// 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;
}
}
// 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
)
);
// Changed from timeControl <-> regular
enabled = true;
}
// 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,70 @@ 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 for Foam::IOerror
exitNow(err);
break;
}
// Emit warning otherwise
[[fallthrough]];
}
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 valid functionObject on all processors
if (!returnReduce(bool(objPtr), andOp<bool>()))
{
objPtr.reset(nullptr);
ok = false;
}
}
@ -924,12 +1219,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;

View File

@ -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