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

Reply via email to