-
Notifications
You must be signed in to change notification settings - Fork 477
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
Avoid exception from destructor while stack unwinding #256
Comments
FWIW I totally agree with http://www.gotw.ca/gotw/047.htm and think that using |
@vadz I sympathise with your point, but it would mean changing behaviours that have been there by-design. Let me use Roger's explanation that every SOCI user should know: The following code:
invokes the actual execution of the database query in the destructor of the soci object returned from AFAIU, you suggest to redesign it to something like this:
(Similar approach is used in https://github.com/chrismanning/ejpp/) Then, SOCI caughts any exceptions in destructor but never rethrows (like I think that would be acceptable change in behaviour in SOCI 4.x. |
Hi, I feel having such change in design will break error management in any program using soci. Even if this change is done in a new major version it will be hard and error prone to switch to it. Is the nothrow(false) annotation not enought to solve this for C++11 compilers? If really we can not use the nothrow(false) annotation, I don't like your proposal of checking error code in soci::session itself like you suggested: I would suggest something like close from std::basic_filebuf you refered to but replace it by execute(). If execute is not called manually destructor will call it and ignore any exception . So the following statement will ignore errors or in short : (sql << "select name, salary from persons where id = "<< id, This is just a quick idea and I didn't check implication with soci::statement returned from sql.prepare Arnaud ----- Mail original ----- De: "Mateusz Łoskot" notifications@github.com @vadz I sympathise with your point, but it would mean changing behaviours that have been there by-design. Let me use Roger's explanation that every SOCI user should know: |
I feel like I'm missing something. What exactly is the problem here? Is there a test case we can base this discussion on? |
@lukeocamden Good question. The issue is just a forward of message I received from Maciej and that I planned to look further. The original discussion took place on the Linkedin C++ group and is a bit lengthy (if you have access to that group, you may read through it). Shortly, a general and recommended rule of not throwing destructors was confronted with existing code that allows throwing destructors, with SOCI as an example. 👍 for Maciej's suggestion from me. Here is concluding word from Maciej in that discussion (hope Maciej doesn't mind me paste it): It's about the lifetime of the temporary.
The actual interaction with the database happens at the end of the full expression; conceptually and to ease visualization, we can think that the actual work happens exactly where the semicolon is at the end of the line above. All sub-expressions above have the interesting property of forcing the ordering of side effects, so indeed, the only way to reach the semicolon is to successfully execute everything else on the left. In other words, if you fail earlier, there is no point to interact with the database at the end. It is true that all involved objects are on the stack, but it is easy to ensure that the database interaction does not happen during stack unwinding. There is really no magic here and all this is guaranteed and explained by the standard. |
Thanks for the background. I'm trying to imagine what usage could go wrong. Is it possible that people are imagining doing something like this:
It should be doable to prevent copy/move construction of x in this way. People still have sql.prepare if they want delayed execution. |
I don't think there is anything especially convoluted here. Suppose you want to store some information about the failure of some operation in the database and so use SOCI while handling some other exception -- currently this would result in an abort. |
If a user wants to contact a database in a destructor, they do this:
|
This is not what I mean. You could instead be using SOCI while already handling an exception (not necessarily in dtor, although could be as well). |
I'm not sure I get it - sorry for not keeping up. Could you illustrate in code? |
for example you manage SQL transaction in a RAII object.
If you lose your database connection right after initialization of transaction an exception will be thrown by
but transaction instance will be destroyed and it will try to rollback session. So a new exception will be thrown by
and your program will be finished |
Shouldn't ~SQLTransaction look like this?
If user code does some database operation in any RAII destructor, they need to prevent exceptions being leaked - this is true even if we don't throw from any SOCI destructor. For example, if the design were changed so that operator<< reports the error by throwing, your program would still crash unless you add a try/catch. |
@ArnaudD-FR example does show it, but it could be even more innocent than this: imagine you use a log framework (think of Now there is nothing wrong with using logging in an exception handler, especially if you take care to put the logging code inside Unfortunately the combination of all these steps is fatally flawed because any error while writing the log message to the database will terminate the program, in spite of all precautions you had taken. And when a combination of reasonable expectations results in an unreasonable behaviour, something is very wrong somewhere. And I'm afraid that in this case, this "somewhere" is SOCI :-( |
Similarly, @vadz, shouldn't your custom logging code look like this:
|
@lukeocamden this was just a stupid example demonstrating that on some case a second exception may be throw by statement destructor |
My point is that the expression "sql << ..." may throw to report an exception, the exact site of the throw statement is not relevant. If you try to access a DB from a destructor then you have to be prepared for the DB code to throw - this is the responsibility of the user. In the logging case, either
or
|
I think we are going away from main subject, listed example might exist, code is never perfect and some complex use cases where 2 exceptions are thrown and not catch might exist. Returning to the subject, as I understand c++11 does not allow destructors to throw exception unless specified. So @mloskot proposed to do a brainstorming to update or not soci to match c++11 requirements. You can read my 2 cents on my first post |
OK - going back to your first post. You say "Is the nothrow(false) annotation not enought to solve this for C++11 compilers?". My answer is a big yes, ie what is already implemented is enough. If a user wishes to access a DB from a destructor, they have the power to decide whether to allow exceptions to escape (and risk std::terminate), or use try/catch to do something more resilient. |
But this has nothing to do with whether SOCI throws from a destructor. auto x = (sql << "select x from y", into(x)); which indeed is convoluted usage. Thanks, Aleksander On Thu, Jul 3, 2014 at 5:23 AM, ArnaudD-FR notifications@github.com wrote:
|
On Thu, Jul 3, 2014 at 5:32 AM, VZ notifications@github.com wrote:
Thanks, Aleksander
|
This aspect of coping with changes between C++03 C++11, as noted by Roger Orr on the mailing list long time ago, has already been taken care of, see #181 & #192 |
On 3 July 2014 20:13, Pawel Aleksander Fedorynski notifications@github.com
@pfedor, thanks for chiming in and I agree with you. @lukeocamden gave an interesting example, but i doubt it's expected to be used in practice. General recommendations like those given by Sutter are good, but they are...general and in our case they smell of a dogma that does not necessarily fit the reality. We should align to technical specification of C++ standard only and according to that, in SOCI, we throw from destructors by design and safely. Finally, we can't simply change the status quo here, unless we aim a different SOCI, really. Addition of the proposed std::uncaught_exception check could be equally turned into assert(!std::uncaught_exception()), so it's purpose sticks out. I'm in favour of merging the proposed #257. |
The problem with #257 is that if you have code like this: Foo::~Foo()
{
try {
// Suppose that Log() ends up using SOCI to write the message to
// the database.
Log("Foo being destroyed");
} catch (...) {
// whatever
}
} and Now, this is probably not a fatal problem, but it's still definitely unexpected and rather nasty because the bug is going to appear "randomly". Whether it's better than working normally most of the time (in this situation) and aborting the program if a database write error occurs is not really clear. Perhaps we should test for |
Swallowing the exception is still wrong IMO. Presumably the user wrote code to contact the DB for a reason. Failing to check for errors (by try/catch) is a bug and the runtime is totally justified in calling std::terminate in this case. This could imaginably be much better than continuing as if nothing is wrong - it could be the difference between some vital diagnostic information being retained in a core file, or lost without a trace. |
Yes, exactly. Using uncaught_exception() would break this perfectly valid I think we should keep things exactly as they are. Cheers, Aleksander On Fri, Jul 4, 2014 at 8:35 AM, VZ notifications@github.com wrote:
|
Given the subtle differences in opinions the proposed changes in SOCI run-time behaviour, I agree to keep things exactly as they are. What about adding assertion? Not necessary, not recommended, neutral? |
Asserting on std::uncaught_exception is wrong because the following code would then stop working:
|
I agree with @lukeocamden, you can not do it in a neutral way |
I should have referred to @vadz suggestion:
IOW, assumption like here:
|
Where you write 'assume no uncaught exception here', std::uncaught_exception may return true if you code is called from a destructor. |
even if you do something like
You will break example given by @lukeocamden because exception was exepected to be caught at higher level |
@lukeocamden That is the point. The assertion will be followed with program termination anyway. @ArnaudD-FR What you mean as "break example"? I simply suggest to document a crash case (assertion is a documentation, and a check active in debug-only build). Nevertheless, there is nothing to break as we talk about program termination. Moreover,
|
My example won't terminate if the user has written try/catch in whatever destructor calls into SOCI. For example:
|
The try/catch above does not really matter, as the execution will get stuck in the Are we arguing about equivalent situation to this?
|
'delete this' in a destructor is no good, but AFAICT SOCI does not do that. I think I've missed something. |
I've updated the code above to replicate SOCI exceptions route.
|
Your code is missing 'noexcept(false)' on ~sql() Other than that your code:
is the same as my earlier example:
In real usage the object whose destructor throws does not live past the end of the statement, because people write it like this:
|
On 8 July 2014 16:56, lukeocamden notifications@github.com wrote:
Yes, the master still needs to build in C++03.
I believe we all agree to keep things as they are and close this Issue/PR |
I definitely agree. |
Maciej notified me about discussion on SOCI at Linkedin C++ Professionals Group, where folks discussed that SOCI still suffers from potential of throwing from destructor while another exception is handled.
Maciej suggested to fix it by checking
std::uncaught_exception()
inref_counted_statement_base::dec_ref()
, and if it returns true, thefinal_action()
should not be called to avoid _any_ interactions with database.The text was updated successfully, but these errors were encountered: