[transact] Handling with user exceptions

Hi, I would like to initiate a discussion on how to manage with user exceptions. Here follows some thougth I'm implementing in Boost.STM. There are two main ways to manage with user exceptions: * exceptions commit transactions - corresponds to the current C++ behavior * exceptions abort on exit - corresponds to rollback The behavior associated to the curent language-like macros is to throw the catched exception. In this case, the exception shouldn't contain references to transaction specific objects. I was wondering if we cannot add a second family of macros that before a user exception leaves the transaction block the transaction is commited. BOOST_TRANSACT_A_ family will abort the transaction on user exception BOOST_TRANSACT_C_ family will commit the transaction on user exception This will allows to explore which both approaches, and see which approach is more usable, or if there is an approach that is better adapted to some particuler contexts. * Exceptions Commit Transactions - How to get this? While trying to commit on destruction we have catched any exception to release the commit_on_destruction variable. commit_on_destruction __comm( __txn ) ; t ry { i f ( false ) ; else / / to for ce a sentence / / t r a n s a c t i o n sentence } catch ( . . . ) { __comm. release ( ) ; throw ; } To make exception commit transactions by default, we should not release the committer. However, we need to avoid exception throw by commit when the transaction has been set as forced to abort or during unwinding exception. commit_on_destruction __comm( __txn ) ; t ry { <sentence > } catch ( . . . ) { i f ( __txn_.forced_to_abort ( ) ) __comm.release ( ) ; else __comm.commit ( ) ; throw ; } We need how to override the default behavior. If we have Exceptions Commit Transactions - we can abort the transaction in two ways: * Explicit try-block to Abort Transactions * Explicitly abort before throw - Explicit try-block to Abort Transactions The user may want the exception abort the transaction while leaving the transaction block. The user must explicitly state that this is the desired behavior by adding a try-catch block as shown below: BOOST_STM_C_TRANSACTION t ry { / / t r a n s a c t i o n block } catch (Ex&) { BOOST_STM_ABORT( ) ; throw ; } BOOST_STM_C_END_TRANSACTION; - Explicitly abort before throw The user may want the transaction to be aborted before throwing an exception. The user may explicitly state that this is the desired either by setting the current transaction as forced to abort, BOOST_STM_ABORT( ) ; throw except ( ) ; or by throwing the exception using the macro : / / . . . BOOST_STM_ABORT_AND_THROW( except ( ) ) ; / / . . . If we have Exceptions Abort on Exit we can commit the transaction as follows: - Explicit try-block to Commit Transactions The user must explicitly state that this is the desired behavior by adding a try-catch block as shown below: BOOST_STM_TRANSACTION t ry { / / t r a n s a c t i o n block } catch (Ex &ex ) { BOOST_STM_COMMIT( ) ; throw ; } BOOST_STM_END_TRANSACTION; boost::exception_ptr error; BOOST_STM_TRANSACTION t ry { / / t r a n s a c t i o n block } catch (Ex &) { error=boost::current_exception(); } BOOST_STM_END_TRANSACTION; if( error) boost::rethrow_exception(error); } Please let me know what do you think? Do you find this useful? Should we provide two families of macros? Best regards, _____________________ Vicente Juan Botet Escribá

Zitat von "vicente.botet" <vicente.botet@wanadoo.fr>:
I would like to initiate a discussion on how to manage with user exceptions. Here follows some thougth I'm implementing in Boost.STM.
There are two main ways to manage with user exceptions: * exceptions commit transactions - corresponds to the current C++ behavior * exceptions abort on exit - corresponds to rollback
I think we should look at this from the user's perspective first and why he would use either of those ways to manage exceptions. this guideline on exception safety can help us a lot in this I think: http://www.boost.org/community/exception_safety.html by the definitions of section 3 there, transactions are nothing more than a vehicle to achieve the "strong" exception guarantee. using "exceptions commit transactions" by your definitino above (which imho does not correspond to current c++ behaviour, if you follow exception guidelines) would mean that you intentionally only use a "basic" exception guarantee for a certain function/transaction scope. why would that be desirable? as section 5 of david abrahams' paper notes, "From a client's point-of-view, the strongest possible level of safety would be ideal.", and then goes on with considerations on when to accept a "basic" guarantee for performance reasons. these don't apply for us, as we already impose the overhead described in paragraph 2 on every operation. Best,

----- Original Message ----- From: <strasser@uni-bremen.de> To: "vicente.botet" <vicente.botet@wanadoo.fr> Cc: <boost@lists.boost.org> Sent: Monday, March 01, 2010 11:39 PM Subject: Re: [transact] Handling with user exceptions
Zitat von "vicente.botet" <vicente.botet@wanadoo.fr>:
I would like to initiate a discussion on how to manage with user exceptions. Here follows some thougth I'm implementing in Boost.STM.
There are two main ways to manage with user exceptions: * exceptions commit transactions - corresponds to the current C++ behavior * exceptions abort on exit - corresponds to rollback
I think we should look at this from the user's perspective first and why he would use either of those ways to manage exceptions.
this guideline on exception safety can help us a lot in this I think: http://www.boost.org/community/exception_safety.html
by the definitions of section 3 there, transactions are nothing more than a vehicle to achieve the "strong" exception guarantee.
using "exceptions commit transactions" by your definitino above (which imho does not correspond to current c++ behaviour, if you follow exception guidelines) would mean that you intentionally only use a "basic" exception guarantee for a certain function/transaction scope.
why would that be desirable? as section 5 of david abrahams' paper notes, "From a client's point-of-view, the strongest possible level of safety would be ideal.", and then goes on with considerations on when to accept a "basic" guarantee for performance reasons.
these don't apply for us, as we already impose the overhead described in paragraph 2 on every operation.
I would make a difference between nested transactions and root transactions. * When a nested transaction throws a user exception the user can handle this exception between the root transaction and the nested transaction. The question is which state of the transaction will be the better to let the user to manage with the user exception? If the nested transaction is rollback the user is unable to know the state of the system when the exception was thrown. But if the transaction is commited, the user will have access to the state as it was when the exception was thrown. The fact that the functions including nested transactions doesn't satisfy even "The basic guarantee: that the invariants of the component are preserved, and no resources are leaked." it is not important as the action has not been commited completly, as only the root transaction will realize the visible commit. * When a root transaction throws a user exception the user don't have any mean using the TM system to achieve a the "strong guarantee; that is, that the operation has either completed successfully or thrown an exception, leaving the program state exactly as it was before the operation started". In this case the rollback of the root transaction seems the best choice. So instead of providing two family of macros we can have a default behavior that differes for root and nested transactions. If the implicit behavior is not desirable we will need two family of macros. Hoping this explanation is enough to justify that we need to play with both behaviors before stating which one is the best default behavior: either user exceptions commit transactions or user exception abort on exit. Best, Vicente

Zitat von "vicente.botet" <vicente.botet@wanadoo.fr>:
There are two main ways to manage with user exceptions: * exceptions commit transactions - corresponds to the current C++ behavior * exceptions abort on exit - corresponds to rollback
I think we should look at this from the user's perspective first and why he would use either of those ways to manage exceptions.
this guideline on exception safety can help us a lot in this I think: http://www.boost.org/community/exception_safety.html
by the definitions of section 3 there, transactions are nothing more than a vehicle to achieve the "strong" exception guarantee.
using "exceptions commit transactions" by your definitino above (which imho does not correspond to current c++ behaviour, if you follow exception guidelines) would mean that you intentionally only use a "basic" exception guarantee for a certain function/transaction scope.
why would that be desirable? as section 5 of david abrahams' paper notes, "From a client's point-of-view, the strongest possible level of safety would be ideal.", and then goes on with considerations on when to accept a "basic" guarantee for performance reasons.
these don't apply for us, as we already impose the overhead described in paragraph 2 on every operation.
I would make a difference between nested transactions and root transactions.
* When a nested transaction throws a user exception the user can handle this exception between the root transaction and the nested transaction. The question is which state of the transaction will be the better to let the user to manage with the user exception? If the nested transaction is rollback the user is unable to know the state of the system when the exception was thrown. But if the transaction is commited, the user will have access to the state as it was when the exception was thrown.
doesn't STM completely roll back an aborted nested transaction so that the root transaction can be committed as if the nested transaction never happened? it should. example: assert(tx_a == 0); transaction{ tx_a=1; try{ transaction{ tx_a=2; throw 5; } }catch(int){} assert(tx_a == 1); } assert(tx_a == 1); the inner transaction scope has exactly the same behaviour as any other function with a "strong" exception guarantee. I don't see a difference between nested and root transactions.
The fact that the functions including nested transactions doesn't satisfy even "The basic guarantee: that the invariants of the component are preserved, and no resources are leaked." it is not important as the action has not been commited completly, as only the root transaction will realize the visible commit.
it satisfies the strong guarantee from the viewpoint of the user, i.e. from inside the (still running) root transaction. that the root transaction is not yet published to other threads or to a persistent state doesn't matter, does it?

----- Original Message ----- From: <strasser@uni-bremen.de> To: <boost@lists.boost.org> Sent: Sunday, March 07, 2010 3:21 PM Subject: Re: [boost] [transact] Handling with user exceptions
Zitat von "vicente.botet" <vicente.botet@wanadoo.fr>:
There are two main ways to manage with user exceptions: * exceptions commit transactions - corresponds to the current C++ behavior * exceptions abort on exit - corresponds to rollback
I think we should look at this from the user's perspective first and why he would use either of those ways to manage exceptions.
this guideline on exception safety can help us a lot in this I think: http://www.boost.org/community/exception_safety.html
by the definitions of section 3 there, transactions are nothing more than a vehicle to achieve the "strong" exception guarantee.
using "exceptions commit transactions" by your definitino above (which imho does not correspond to current c++ behaviour, if you follow exception guidelines) would mean that you intentionally only use a "basic" exception guarantee for a certain function/transaction scope.
why would that be desirable? as section 5 of david abrahams' paper notes, "From a client's point-of-view, the strongest possible level of safety would be ideal.", and then goes on with considerations on when to accept a "basic" guarantee for performance reasons.
these don't apply for us, as we already impose the overhead described in paragraph 2 on every operation.
I would make a difference between nested transactions and root transactions.
* When a nested transaction throws a user exception the user can handle this exception between the root transaction and the nested transaction. The question is which state of the transaction will be the better to let the user to manage with the user exception? If the nested transaction is rollback the user is unable to know the state of the system when the exception was thrown. But if the transaction is commited, the user will have access to the state as it was when the exception was thrown.
doesn't STM completely roll back an aborted nested transaction so that the root transaction can be committed as if the nested transaction never happened? it should.
example:
assert(tx_a == 0);
transaction{
tx_a=1;
try{ transaction{ tx_a=2; throw 5; } }catch(int){} assert(tx_a == 1);
if user exceptions commit transaction we have assert(tx_a == 2);
}
assert(tx_a == 1);
the inner transaction scope has exactly the same behaviour as any other function with a "strong" exception guarantee. I don't see a difference between nested and root transactions.
If the user want to ablor the iner transaction it could do transaction{ tx_a=1; try{ transaction{ tx_a=2; force_to_abort();throw 5; } }catch(int){} assert(tx_a == 1); } Both know that any default behavior will have its advantages and liabilities. I'm trying to setup an environement that could allow us to know if there is a default behavior that feets most of the user contexts. If we provide only one possibility, we will not be able to see how the other possibility will do. BTW, the default behavior on the Draft C++ Transactional Memory documment is to commit the transaction on exception.
The fact that the functions including nested transactions doesn't satisfy even "The basic guarantee: that the invariants of the component are preserved, and no resources are leaked." it is not important as the action has not been commited completly, as only the root transaction will realize the visible commit.
it satisfies the strong guarantee from the viewpoint of the user, i.e. from inside the (still running) root transaction. that the root transaction is not yet published to other threads or to a persistent state doesn't matter, does it?
It matters for me. We don't need this strong guarantee because we have yet a resource that allows us to satisfy the strong guarantee once the root transaction is commited or aborted. Vicente

Zitat von "vicente.botet" <vicente.botet@wanadoo.fr>:
assert(tx_a == 0);
transaction{
tx_a=1;
try{ transaction{ tx_a=2; throw 5; } }catch(int){} assert(tx_a == 1);
if user exceptions commit transaction we have assert(tx_a == 2);
even if the inner transaction was rolled back by the user exception(which is the current behaviour of the macros)? then I'd consider this a bug. what other transactional system behaves that way? SQL databases that support nested transactions(called savepoints in SQL) don't. if you want to discuss again if the macros should commit or roll back on user exceptions that's another discussion, but IF a nested transaction is rolled back it must not have any effects on the outer transaction if the outer transaction is committed. this would make nested transactions unmanageable, since the user would have to know if a transaction is nested or not. consider e.g. /// Provides strong exception guarantee void f(){ transaction{ tx_a=2; if(something) throw something; } } void g(){ try{ f(); }catch(...){ //ok, that didn't work, try something else. but don't rethrow //... } } void h(){ transaction{ tx_other=123; g(); } } int main(){ g(); //ok. in case of an exception, tx_a is unchanged. h(); //error. in case of an exception an inconsistent state is committed. }
BTW, the default behavior on the Draft C++ Transactional Memory documment is to commit the transaction on exception.
I have read it and I find the argument for this to be very weak: "The TM handler could either commit or abort the current transaction when an exception is raised. The justification for committing when an exception occurs is that the memory retains its values as seen at the time of exception. If the TM handler aborts the TM region, then the memory may be inconsistent as some of the values may be rolled back. If an object in C++ is thrown and if this object is undone during an abort, then the object may be inconsistent when a handler outside the TM region is examining this object." this seems to be the entire justification. imho this is no justification at all, since you can experience exactly the same thing with regular c++ "strong" exception guarantees. example: void f(){ try{ this->a=1; throw my_exc(this->a); }catch(...){ //strong guarnatee, undo: this->a=0; } } void g(){ try{ f(); }catch(my_exc &e){ assert(this->a == e.a); //fails! } }
The fact that the functions including nested transactions doesn't satisfy even "The basic guarantee: that the invariants of the component are preserved, and no resources are leaked." it is not important as the action has not been commited completly, as only the root transaction will realize the visible commit.
it satisfies the strong guarantee from the viewpoint of the user, i.e. from inside the (still running) root transaction. that the root transaction is not yet published to other threads or to a persistent state doesn't matter, does it?
It matters for me. We don't need this strong guarantee because we have yet a resource that allows us to satisfy the strong guarantee once the root transaction is commited or aborted.
not sure if understand your point here. the exception guarantee is given to the (immediate) caller of the function. the caller is not aware if it is called within a root transactino or not. all it knows is: if there is an exception, all changes are undone. why does the root transaction matter at this point?

Zitat von strasser@uni-bremen.de:
if you want to discuss again if the macros should commit or roll back on user exceptions that's another discussion
to this point, we've discussed it before and I thought we agreed to the following: abort on exception, default-behaviour: transaction{ throw my_exc(); //rolls back } commit on exception: transaction{ transaction_manager::current_transaction().commit(); // (1) throw my_exc(); //does not roll back } not agreed upon? (1) if you want, we can introduce a "keyword" for commit and throw, just like intel does for abort and throw.
participants (2)
-
strasser@uni-bremen.de
-
vicente.botet