Boost logo

Boost :

Subject: Re: [boost] [transact] transaction language macros (was: Re: [transact] code in sandbox)
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-02-21 10:17:39


----- Original Message -----
From: <strasser_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Friday, February 19, 2010 10:06 PM
Subject: Re: [boost] [transact] transaction language macros (was: Re: [transact] code in sandbox)

>
> Zitat von "vicente.botet" <vicente.botet_at_[hidden]>:
>
>> int priority=0;
>> begin_transaction{
>> //...
>> }retry{
>> stm::set_priority(priority++);
>> }end_retry;
>
> inside the retry-clause the transaction object is destructed and no
> new one is constructed yet. STM could provide an API for setting the
> priority of the next transaction, using thread-specific storage to
> support this.

I would prefer the transaction be available on retry. Of courset, this need to have a restart function.
 
> I think the only way to support this on the macro level is using a
> boost::optional to hold the transaction, which probably introduces
> runtime overhead. (and I think another problem like the one below
> regarding "___control=0")
>
>> * committing the transaction and rethrowing a specific exception:
>>
>> begin_transaction{
>> try{
>> //...
>> throw my_exc();
>> //...
>> }catch(my_exc &){
>> commit();
>> throw;
>> }
>> }end_transaction;
>
> it seems to me that this already is (unintentionally) supported.
> (using active_transaction().commit()).
>
> the scope is exited with an exception, so it does not commit again.
> commit_on_destruction is nullified.

Oh I see, I miss that.
 
> only if the user commits manually and does not throw an exception
> there is a problem, but I think we can leave this case undefined. just
> like calling transaction::commit() two times in a row.

I agree. the user shouldn't need to commit explicitly.
 
>>
>> * rollback the transaction and ignore a specific exception:
>>
>> begin_transaction{
>> try{
>> //...
>> throw my_exc();
>> //...
>> }catch(my_exc &){
>> rollback(); or set_rollback_only();
>> }
>> }end_transaction;
>
> hmm...why?
> can't you catch the exceptionb outside of the scope?
>
> try{
> begin_transaction{
> throw my_exc();
> }end_transaction;
> catch(my_exc &) {
> //ignore
> }

Yes this do the same. The documentation should warn about the preceding usage.
 
>> * Should the following compile?
>>
>> begin_transaction
>> statement1;
>> statement1;
>> end_transaction;
>>
>
> it currently does. if we want to adhere to the syntax of a language extension:
>
> transaction /statement/
>
> we could enforce this by wrapping the user code in
>
> do
> //user code
> while(false);
>
> which supports a single statement and compound statements, but not
> multiple statements.
> I'm in favor of this. I used something similar to enforce the
> semicolon after "end_transaction".

You can not add another loop otherwise the break/continue will not exit the transaction loop.
    if(false);else
works as expected.

>> * Just a remark: the following case doesn't do what the user could
>> expect as the user is unable to set the variable __control to 0. It
>
> this only affects the case the user "break"s from the retry clause.
> see my other email.
>
>> begin_transaction{
>> try{
>> //...
>> }catch(boost::transact::isolation_exception & ex){
>> // do something before retry
>> }
>> }end_transaction;
>
> if we move the __control=0; from the catch(isolation_exception &)
> clause up to the catch(...) one, the above use case seems OK.
> neither in the case that the whole transaction scope is exited by an
> exception , nor in the case that the retry-loop is continued the value
> of __control matters. it's only set to 0 for the "break out of retry"
> case.

There is yet a drawback to your current implemntation:
* when the commit done on the destructor of the commit_on_destruction variable fails, you will throw an exception on a destructor. If the destructor is called due to a thrown exception the program will terminate. So, ~commit_on_destruction() should not throw.
If ~commit_on_destruction() don't throw the isolation_exception can not be throw in this case, so the retry is not activated.

I have added a parameter throw_catched to commit_on_destruction(), so not only ~commit_on_destruction() don't throw but also notified if the exception has been catched.

template<class TxMgr>
struct commit_on_destruction{
    explicit commit_on_destruction(basic_transaction<TxMgr> &tx
    , bool &catched) // (NEW)
        : tx(&tx),
        exception_catched(catched) {} // (NEW)
    ~commit_on_destruction(){
        if(this->tx) {
            try {// (NEW)
                exception_catched=false;// (NEW)
                this->tx->commit();
            } catch(...) {// (NEW)
                exception_catched=true;// (NEW)
            }// (NEW)
        }
    }
    void nullify(){
        this->tx=0;
    }
private:
    basic_transaction<TxMgr> *tx;
    bool& exception_catched;// (NEW)
};

With this new parameter we can throw after the destructor is called. We need to add a new block, which will throw if the exception was catched.

        bool ___exception_catched =false; \ NEW
        { \ NEW
          boost::transact::basic_transaction<TXMGR> ___tx; \
          boost::transact::detail::commit_on_destruction<TXMGR> ___commit(___tx,___exception_catched ); \ NEW
          // as before

        } \ NEW
        if (must_throw) {\ NEW
          throw boost::transact::isolation_exception(); \ NEW
        }\ NEW

* return could not be transparent. The following code could return even if the transaction
int f() {
    begin_transaction
        return variable;
    end_transaction;
}

Note that when we do a return in the transaction block, the lines
        if (must_throw) {\ NEW
          throw boost::transact::isolation_exception(); \ NEW
        }\ NEW

will not be executed, only the destructor are executed. As ~commit_on_destruction() must catch the possible isolation exception, if the commit fails there will be no exception. So return will return from the function even if the commit fails. We need to commit explicitly before returning.

int f() {
    begin_transaction
        RETURN(variable);
    end_transaction;
}

RETURN(var) must commit the transaction and return the parameter evaluated on the context of the transaction. But you need to commit using the commit_on_destruction variable, because otherwise you will commit twice. Boost.STM has added a commit function to the commit_on_destruction class

    void commit() {
        if (tx_!=0&&!commited_) {
            tx_->commit();
            commited_=true;
        }
    }

Boost.STM defines a macro

#define BOOST_STM_RETURN(TX, EXPRESSION) \
    return boost::stm::detail::commit_and_return(BOOST_STM_VAR_DESTR(TX), EXPRESSION)

template <typename TX, typename T> T commit_and_return(TX &t, T const& var) {
    T tmp(var);
    t.commit();
    return tmp;
}

You can inspire from this to solve the same issues on Boost.Transact.

Please let me know if I missed something.

I have resumed in the following pseudo code the code generated by my macros depending on whether
* the current transaction block is in a loop IN_LOOP
* there are specific exception hadlers HANDLER_SEQ
* there is a specific retry (RETRY)

>>> frame transaction_block =
    keyword<transaction> statement<BODY>
    [ handler_seq<HANDLER_SEQ> ]
    [ keyword<retry> compound_statement<RETRY> ]
>>> var IN_LOOP = ... current transaction block in a loop
>>> transaformation
{
    bool __stop = false;
>>> if in_loop
    boost::stm::detail::control_flow __ctrl;
>>> endif
    do {
        boost::stm::transaction __TX;
        try{
            bool __catched=false;
            {
                boost::stm::detail::commit_on_destruction destr(__TX, __stop, __catched);
                try{
>>> if IN_LOOP
                    do {
                        BOOST_STM_VAR_CTRL(TX)=boost::stm::detail::break_;
>>> endif
>>> if present(HANDLER_SEQ)
                        try {
>>> endif
                            BODY
>>> present(HANDLER_SEQ)
                        }
                        HANDLER_SEQ
>>> endif

>>> if IN_LOOP
                        __ctrl=boost::stm::detail::none;
                    } while ((BOOST_STM_VAR_CTRL(TX)=boost::stm::detail::continue_),false);
>>> endif
                    destr.commit();
                } catch(...) {
                    destr.release();
                    throw;
                }
            }
            if (catched) {
                throw boost::stm::aborted_tx("commit throw");
            }
            break;
        } catch (boost::stm::aborted_tx &) {
            if (TX.is_nested()) throw;
            TX.restart();
            RETRY
        }
    } while(!stop);

>>> if IN_LOOP
    if (ctrl==boost::stm::detail::continue_) continue;
    else if (BOOST_STM_VAR_CTRL(TX)==boost::stm::detail::break_) break;
>>> endif

}
>>> end_frame

Afterwards I think that it will be very useful if we had a meta-language that allowing code transaformations. With a such meta-language we could just write

transaction {
    ...
} retry {
    ...
}

or

transaction {
    ...
} catch (Ex1& ex) {
    ...
} retry {
    ...
}

Best,
Vicente


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk