All,
Historically, exception handling looked this way at the JRD level:
try
{
tdbb->tdbb_status_vector = user_status;
}
catch (const Exception& ex)
{
return error(user_status);
}
Errors were raised via ERR_post() which *appends* to tdbb_status_vector
and then throws. Errors were reposted via ERR_punt() which just
re-throws tdbb_status_vector.
It allowed different errors (I don't mean primary/secondary error codes,
but really independent errors) to be subsequently posted to the status
vector and then returned to the user. This ability seems to be broken
since v2.x though, because:
- We have a tendency to replace ERR_* calls with direct
exception::raise() calls.
- Our commonly used stuff_exception() routine overwrites the status
vector instead of appending there.
This issue remains invisible mostly because it's quite rare in practice.
I can think of two possible scenarios:
1) Lock manager error is returned from the LCK_* call via
tdbb_status_vector and then a proper higher level exception is thrown.
But now we treat this as a minor bug and clear tdbb_status_vector in
such cases. Also, this is possible only with ERR_post(), which used less
nowadays.
2) Some error is handled by the looper and another error is thrown while
unwinding the request. This is a more common situation, but most
unwinding exceptions are thrown from VIO_verb_cleanup() and we had a
bugcheck reported in this case, which hides the real problem.
This problem became better visible in v3 after I removed this bugcheck
in favor of transaction invalidation + proper error reporting. Now we
have the backout error reported but the original error (which initiated
a backout) is lost.
Here's a demonstration of the problem:
catch (const Exception& ex)
{
ex.stuff_exception(tdbb->tdbb_status_vector);
<some code>;
throw;
}
- If <some code> is trivial, the logic is absolutely the same as
ERR_punt() -- no problems here.
- If <some code> throws, then we have exception A residing in
tdbb_status_vector but exception B is thrown. Quite inconsistent, given
that we still have some tdbb_status_vector[1] checks in the code.
Finally, at the JRD level, user_status will be overwritten with
exception B, thus losing the original exception.
- If <some code> throws, catches and re-throws, then tdbb_status_vector
will be overwritten inside <some code>. Again, the original exception is
lost.
At the first glance, one might think that all we need is to change
stuff_exception() to append instead of overwriting. But it's not really
so. Given how many times we may catch and re-throw within the call
stack, I'd expect lots of duplicated items inside the status vector. And
proper duplication checking (with strcmp for string args) looks like an
overkill. There may be other unexpected side effects in this solution too.
A long-term solution would include:
- fixing all direct tdbb_status_vector checks
- removing tdbb_status_vector completely
- removing stuff_exception too
But that's surely for another day. For v3, I can think only about the
following approach:
catch (const Exception& ex)
{
Arg::StatusVector error(ex);
try
{
<some code>
}
catch (const Exception& ex2)
{
error.append(ex2);
}
error.raise();
}
Of course, in many cases it's unnecessary and would just pollute the
code. So we need to carefully analyze every catch() block for possible
side effects like throwing exceptions from inside. Fortunately, most of
them are not really important in practice, so it's not a showstopper.
A more complex case is the looper which holds an active exception inside
tdbb_status_vector for the whole duration of the unwinding process, but
it also looks solvable.
I have draftly implemented this approach for the looper and it seems to
work, now I see both original and backout errors reported. This resolves
the mostly visible reason of the problem.
But maybe someone can suggest a better solution?
Dmitry
------------------------------------------------------------------------------
Dive into the World of Parallel Programming. The Go Parallel Website,
sponsored by Intel and developed in partnership with Slashdot Media, is your
hub for all things parallel software development, from weekly thought
leadership blogs to news, videos, case studies, tutorials and more. Take a
look and join the conversation now. http://goparallel.sourceforge.net/
Firebird-Devel mailing list, web interface at
https://lists.sourceforge.net/lists/listinfo/firebird-devel