Exceptions improvement
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Be nice to others! Respect the FreeCAD code of conduct!
Exceptions improvement
We have been discussing about this in this thread:
https://forum.freecadweb.org/viewtopic. ... 30#p171864
After considering the input, I have decided to structure this improvement of exceptions into 3 stages in at least 3 PR:
1. Improve exceptions so that function/file/line number information is added in the report view.
2. Improve exceptions so that a translatable message is used in the UI.
3. Improve exceptions so that no information is lost, the message is not modified because the exception traveling from c++ to python.
This should allow not to diverge too much and have much more tight code reviews of such a core code. Which should benefit everybody.
https://forum.freecadweb.org/viewtopic. ... 30#p171864
After considering the input, I have decided to structure this improvement of exceptions into 3 stages in at least 3 PR:
1. Improve exceptions so that function/file/line number information is added in the report view.
2. Improve exceptions so that a translatable message is used in the UI.
3. Improve exceptions so that no information is lost, the message is not modified because the exception traveling from c++ to python.
This should allow not to diverge too much and have much more tight code reviews of such a core code. Which should benefit everybody.
Re: Exceptions improvement
First PR - stage 1
https://github.com/FreeCAD/FreeCAD/pull/721
1. Enable automatic harvesting of information (function, file, line) when throwing the exception via macro:
Examples:
THROWM(Exception, "BSpline GeoId is out of bounds.")
THROWM(ValueError, "BSpline GeoId is out of bounds.")
THROW(AbortException)
Output:
a) Python Console (what()):
App.ActiveDocument.Sketch004.modifyBSplineKnotMultiplicity(16,3,0)
Traceback (most recent call last):
File "<input>", line 1, in <module>
Base.FreeCADError: FreeCAD exception thrown (BSpline GeoId is out of bounds.)
b) Report View (report()):
Exception (Thu Apr 27 19:15:24 2017): BSpline GeoId is out of bounds. in bool Sketcher::SketchObject::modifyBSplineKnotMultiplicity(int, int, int) in src/Mod/Sketcher/App/SketchObject.cpp:4102
2. Extend the basic framework so as to allow more control over the mangling of the message introduced by the user, setting the basis to allow, where needed, to preserve the original message while allowing full legacy behaviour.
From the related discussion thread:
This pull request does not deal with translations.
After reconsidering previous topics and looking at the error messages of some rare translations (like access violation), I have reached the conclusion that potentially any exception type can have a meaningful message for a UI user (in that case it was save your work and close freecad, or something similar, which is very relevant for a user who might lose his work). For sure, I still maintain that not every translation is to be translated (and for rare exception types it is rather the exception, not the rule to have a non-very-technical message useful for the user). However, in view of this, I am going to try in stage 2 to make this happen without having to create any specialized exceptions at all. Just reusing the infraestructure of stage1. Let's see if I manage.
Let me know your impressions.
https://github.com/FreeCAD/FreeCAD/pull/721
1. Enable automatic harvesting of information (function, file, line) when throwing the exception via macro:
Examples:
THROWM(Exception, "BSpline GeoId is out of bounds.")
THROWM(ValueError, "BSpline GeoId is out of bounds.")
THROW(AbortException)
Output:
a) Python Console (what()):
App.ActiveDocument.Sketch004.modifyBSplineKnotMultiplicity(16,3,0)
Traceback (most recent call last):
File "<input>", line 1, in <module>
Base.FreeCADError: FreeCAD exception thrown (BSpline GeoId is out of bounds.)
b) Report View (report()):
Exception (Thu Apr 27 19:15:24 2017): BSpline GeoId is out of bounds. in bool Sketcher::SketchObject::modifyBSplineKnotMultiplicity(int, int, int) in src/Mod/Sketcher/App/SketchObject.cpp:4102
2. Extend the basic framework so as to allow more control over the mangling of the message introduced by the user, setting the basis to allow, where needed, to preserve the original message while allowing full legacy behaviour.
From the related discussion thread:
This pull request does not deal with translations.
After reconsidering previous topics and looking at the error messages of some rare translations (like access violation), I have reached the conclusion that potentially any exception type can have a meaningful message for a UI user (in that case it was save your work and close freecad, or something similar, which is very relevant for a user who might lose his work). For sure, I still maintain that not every translation is to be translated (and for rare exception types it is rather the exception, not the rule to have a non-very-technical message useful for the user). However, in view of this, I am going to try in stage 2 to make this happen without having to create any specialized exceptions at all. Just reusing the infraestructure of stage1. Let's see if I manage.
Let me know your impressions.
Re: Exceptions improvement
Report view should have meaningful (including regular user related) messages that much is true. But i would imagine some users don't have it opened and rather leave the internals of FreeCAD to autosave mechanism (to deal with it).abdullah wrote:I have reached the conclusion that potentially any exception type can have a meaningful message for a UI user (in that case it was save your work and close freecad, or something similar, which is very relevant for a user who might lose his work).
Re: Exceptions improvement
I have been looking to your containers exception mechanism (to basically see if I can get inspired for the main exception one). I see how you cleverly set the right python objection from its c++ counterpart.DeepSOIC wrote: ... summoning
How are you using these python exceptions? I mean, are you differentiating between types somewhere at returning from python execution?
I mean, I guess you must use the interpreter to execute any python command from c++, so then you execute your python command that eventually calls the c++ code, that generates a c++ exception, that cleverly gets converted into a python exception of the matching type, which gets returned to the interpreter...
...but you did not modify the interpreter and it only checks for PyExc_SystemExit internal python exception, the rest are processed via pyException. So you must be using the pyException mechanism...
...you are indeed catching Py::Exception in a couple of places, but you just Report the exception.
I am missing something?
- DeepSOIC
- Veteran
- Posts: 7896
- Joined: Fri Aug 29, 2014 12:45 am
- Location: used to be Saint-Petersburg, Russia
Re: Exceptions improvement
I don't, yet. It's an API, and it's handy when you can distinguish exceptions from python code. I haven't established a mechanism to convert py exceptions back into the same c++ ones.abdullah wrote:How are you using these python exceptions?
Re: Exceptions improvement
Ok. Sure is handy! I understand now. I thought I was missing somethingDeepSOIC wrote:I don't, yet. It's an API, and it's handy when you can distinguish exceptions from python code. I haven't established a mechanism to convert py exceptions back into the same c++ ones.abdullah wrote:How are you using these python exceptions?
Re: Exceptions improvement
Second PR - Stage 3 (yes I skipped Stage 2)
This PR:
https://github.com/FreeCAD/FreeCAD/pull/739
Proof of concept. For review.
It is a possible solution to bypass information via python using setObject instead of setString, while still being compatible with any objection raised using setString. I uses a Python Dictionary to convey the c++ extra information thru python, so as to enable reconstruction of the original c++ exception after Python.
I skipped Stage 2, because it makes more sense to me today to solve this problem first.
This PR:
https://github.com/FreeCAD/FreeCAD/pull/739
Proof of concept. For review.
It is a possible solution to bypass information via python using setObject instead of setString, while still being compatible with any objection raised using setString. I uses a Python Dictionary to convey the c++ extra information thru python, so as to enable reconstruction of the original c++ exception after Python.
I skipped Stage 2, because it makes more sense to me today to solve this problem first.
Re: Exceptions improvement
I am not a fan of communicating to the end user through exceptions. That makes coding for fellow developers feel like flying through a meteroid field. The argument, it would reduce necesary branch coverage testing, is only valid to those who do not test the exception branches. A cheap trick to justify test gaps. Thus, the more often you use that as a "user interface", the bigger the technical debt.
Please find a solution that does not introduce additional macros (c.f. motivation for modules by Gabried Dos Reis) and that allows me as a developer, for every line where I want to throw something, to introduce a unique exception class with the naming scheme e.g.
Having functions return structs / enums / documented integers / optionals for other coders makes method contracts explicit and keeps them local. Throwing precise technical error IDs instead of source lines is in time of GIt great for issue handling and recognition of duplicates or regressions. The user can receive all relevant output through log/console instructions, not through exception stack traces. Let's conquer emergency situations via regular autosave and RAII, not by asking the user to extract content from the system when it is already in a corrupted state.
Thank you and kind regards
Dennis
exactly
Please find a solution that does not introduce additional macros (c.f. motivation for modules by Gabried Dos Reis) and that allows me as a developer, for every line where I want to throw something, to introduce a unique exception class with the naming scheme
Code: Select all
<hosting class>_ERR_<GUID without hyphens>
Code: Select all
AttachExtension_ERR_84d3f27d1c234981a53g2468h31eacf5
AttachExtension_ERR_8h22e39ecdab423g81a7847622339cb7a
AttachExtension_ERR_1a92d667f5g24451b4221742383082b5
Thank you and kind regards
Dennis
abdullah wrote: [source: https://forum.freecadweb.org/viewtopic. ... 99#p171199, ...] Note that I had to comment out the catch(Base:Exception).
Why?
I had to do that because:
1. The one ultimately controlling the setting of the flag (and that will clear it if not receiving what it expects) is: Document::_recomputeFeature(DocumentObject* Feat) #5
2. This function will reset any flag set unless: a) the return code is not DocumentObject::StdReturn or b) a Base:Exception was thrown during the execution of the Feature->recompute() #4
3. Feature->recompute() #4 will return whatever #3 returned
4. #3 will return whatever #2 returned
5. #2 will return whatever #1 returned
6. #1 always, unconditionally returns StdReturn
7. at #0, the commented Base::Exception would catch my Base::ValueError exception that is derived from Base::Exception, thereby removing any hope of propagating the error information to #5
Now somebody took extraordinary care to catch all possible exceptions in #0 and return an "App::DocumentObjectExecReturn" which is nevertheless ignored in #1.
And that is the ultimate reason why I am writing this. Is there any reason for this? Can I just propagate all Base::Exceptions as with the commented code?
I also can catch first a Base::ValueError to re-throw the same and avoid the catching by Base::Exception that may be needed for other exceptions, but in such a case I am not returning the "App::DocumentObjectExecReturn". [...]
exactly
Last edited by CADennis on Mon May 08, 2017 6:56 am, edited 2 times in total.
Re: Exceptions improvement
Continuing the thread from the PR:
Since the suggestion in https://github.com/FreeCAD/FreeCAD/pull ... 86295415df isn't a convenient way and also doesn't support exception types of external modules I propose a factory class based on templates.
In FreeCADBase we have the class AbstractProducer. Now the template sub-class can look like this:
Since the suggestion in https://github.com/FreeCAD/FreeCAD/pull ... 86295415df isn't a convenient way and also doesn't support exception types of external modules I propose a factory class based on templates.
In FreeCADBase we have the class AbstractProducer. Now the template sub-class can look like this:
Code: Select all
struct ExceptionInfo {
std::string typename;
std::string function;
std::string message;
...
};
class AbstractExceptionProducer : public AbstractProducer
{
public:
AbstractExceptionProducer () {}
~AbstractExceptionProducer() {}
// just implement it
void* Produce () const {
return nullptr;
}
virtual void raiseException(const ExceptionInfo& info) const = 0;
};
template <class CLASS>
class ExceptionProducer : public AbstractExceptionProducer
{
public:
ExceptionProducer ()
{
ExceptionFactory::instance()->AddProducer(typeid(CLASS).name(), this);
}
virtual ~ExceptionProducer (){}
void raiseException(const ExceptionInfo& info) const
{
CLASS c;
// set data from info
...
throw c;
}
};
Code: Select all
// register exception types
new ExceptionProducer<Base::Exception>;
new ExceptionProducer<Base::XMLBaseException>;
new ExceptionProducer<Base::XMLParseException>;
...
Code: Select all
// catch and save the original exception type
...
catch (const Base::Exception& e) {
std::string type = typeid(e).name();
...
// pass info to Python
PyErr_SetObject(...);
return 0;
}
Code: Select all
// Restore the C++ exception
Base::ExceptionInfo info = ... // get from Python structure
Base::ExceptionFactory::instance()->raiseException(info);
Re: Exceptions improvement
and not that you usually don't rock, but here you really rockwmayer wrote:I propose a factory class based on templates.
I will give it a more attentive look and try to produce a good full solution