Boost logo

Boost :

Subject: Re: [boost] [transaction] New Boost.Transaction libraryunderdiscussion
From: Stefan Strasser (strasser_at_[hidden])
Date: 2010-01-20 21:55:45


Am Thursday 21 January 2010 00:49:25 schrieb vicente.botet:
> >> solves. How can you ensure that each resource has an equivalent stack of
> >> local nested transactions if you create them only when the application
> >> access a resource on the context of a global transaction?
> >
> I've seen that call. This ensure that you have a complete stack on the
> moment of the resource_transaction, but not when the application commit.
> This means that you can have resources that can participate on the
> transaction on the outer levels but not on the inner ones. As this
> resource will not be commited when the inner transaction is commited, we
> are unable to see conflicts with other transactions respect to this
> resource.

what conflict can the other resource cause if there is no nested transaction
in it?

global_root_tx( resource1_root_tx, resource2_root_tx)
        ^ ^
         | |
         | |
global_nested_tx(resource1_nested_tx, none)

global_nested_tx.commit():
resource1_nested_tx is published into resource1_root_tx. how can this call
cause a conflict in resource2?

> > how do you construct a transaction manager (and the resource managers
> > used by it) at the point of (lazy) singleton construction if you don't
> > have any constructor arguments?
>
> The answer was already in my preceding post. If basic_transaction_manager
> is not able to define this function, we can make basic_transaction_manager
> a mixin, that will have the final transaction_manager as parameter. Note
> the new parameter Final.
>
> template<class Final, class Resources,bool Threads=true,bool TThreads=true>
> class basic_transaction_manager {
> static Final& instance() { return Final::instance(); }
> ...
> }

I don't understand what difference that makes. I thought your point was that
the transaction manager should be stored as an invariant singleton.
How is that possible, with or without mixin? What does the mixim accomplish?
What is the difference between instance() and the current active()?

> > think of loc/loc2 not as classes but as typedefs. (they really are, they
> > are only classes to work around the lack of template aliases in C++98.).
>
> I have a question. How loc finds out the persistent resource manager?

TxMgr::resource<ResourceTag>()
(this relates to the following question).

>
> > 2. why shouldn't it be possible to do:
> >
> > {
> > open and use database in file db1.db
> > }
> > {
> > open and use database in file db2.db
> > }
> > ?
>
> This could be a good use case justifying the bind function of
> basic_transaction_manager, but IMO this operation corresponds to something
> I would expect associated to the database resource manager. But maybe I'm
> wrong.

at the moment resource managers cannot be de-registered from the transaction
manager(in order to be destructed and then re-constructed using another
database file).
this is not part of the TransactionManager concept though, you could write a
transaction manager that supports that.

but looking at the overall picture, the user must be able to construct his
resources at some point at run time, so either:

1. TxMgr::active() can not be an invariant singleton and must be able to throw
a no_active_transaction_manager exception

or

2. TxMgr::resource<Tag> can not be an invariant singleton and must be able to
throw.

I went with 1, we could also use 2 if you see an advantage with that approach
(I don't at the moment).

on a seperate note, you could argue that TxMgr::active() could have an
existing transaction manager as a precondition and cause undefined behaviour.
it's hard to draw the line which programming errors should cause exceptions
and which are allowed to crash.
(precendent: std::vector::operator[] vs. std::vector::at())

> I'm realy sorry to report at this level. The concepts are important as well
> as the details at the interface level. Note however that with the

there is nothing wrong with discussing the implementation details and you're
welcome to do so, but we should not change the concepts in favor of an
implementation detail.
I understood your argument for returning a pointer from active_transaction()
as doing just that.

> > when the active transaction is needed for an operation, for example by a
> > basic_loc to access a persistent object, there are generally two cases:
> >
> > a) "I need a transaction for the following operation, if there is none an
> > exception must be thrown." for example: writing to a transactional
> > object.
> >
> > write_to_object(txmgr.active_transaction()); //throws
>
> what can the application do with the exception thrown? terminate?

or continue doing something else. I don't think it is acceptable for code like
the following (outside of a transaction scope):

loc<pers_type const> l=...;
std::cerr << l->value << std::endl;

to be ok but code like the following to cause undefined behaviour:

loc<pers_type> l=...;
std::cerr << l->value << std::endl;

so it throws, even though it is a programming error.
I guess there are similar subtle cases in your library.

>
> > b) "although I could do this operation without a transaction, if there is
> > one, another code path must be followed." for example: reading from a
> > transactional object.
> >
> > if(txmgr.has_active_transaction()){
> > read_transactional();
> > }else{
> > read_global();
> > }
>
> This seems reasonable.
>
> > especially case a) is much more verbose using your interface. every
> > operation that requires a transaction (which is most of them) has to
> > check for a 0 return and manually throw an exception.
>
> Well in this case it seems that both operations should be provided one that
> throws an the other that returns 0 if there is no active transaction .

yes, I went with
if(txmgr.has_active_transaction()) read(txmgr.active_transaction());

if you prefer

if(TxMgr::transaction *tx=txmgr.try_get_active_transaction()) read(*tx);

I guess that's only a matter of style. but there should be a function like the
current active_transaction() that requires an active transaction and throws
otherwise.

>
> > you could make up an example that uses 2 different loc's, but a much
> > simpler example is 2 different transaction's:
> > an application might use Boost.Persistent to store some application
> > state on disk. then, in a complete different part of the application,
> > in another translation unit, someone tries to use Boost.STM and fails,
> > because "transaction" is already configured to use Boost.Persistent.
> > (either because the translation unit includes a header that uses
> > Boost.Persistent or because of a linker error because of One Definition
> > Rule violation).
>
> I think I start to understand what you are looking for. Are you saying that
> transaction_manager will be part of the interface the Persistent library
> (persistent::transaction_manager) and not of the of Transaction library?

no, see below.

>
> typedef basic_transaction_manager<vector<persistent::resource_manager,
> stm::resource_manager>> transaction_manager;

now you're using a typedef. your argument I was responding to was why it is
not sufficient to use one global define instead of a typedef.

you suggested something like:

#define basic_transaction_manager<...> TRANSACTION_MANAGER

class transaction{
  ...{
    TRANSACTION_MANAGER::active()->...();
  }
}

for the reasons mentioned(different parts of the application using
Boost.Transaction in different configurations using different resource
managers, linked libraries, One Definition Rule, etc) this will fail in these
cases. that's why the current code is instead:

template<class TxMgr>
class basic_transaction{
  ...{
    TxMgr::active()->...()
  }
}

#define basic_transaction_manager<...> TRANSACTION_MANAGER

typedef basic_transaction<TRANSACTION_MANAGER> transaction;

the transaction-typedef is not a definition and can be local to the part of
the application using the library, and can be typedef'd to another
transaction manager in another part of the application.

>
> should works for them.
>
> I have another use case that is quite close to yours: an application might
> use Boost.Persistent to store some application state on disk. Then, in a
> complete different part of the application, in another translation unit,
> someone tries to use Boost.Persistent and don't fails at compile time
> neither at link time, but at run time, because both parts uses the same
> class and have binded twice. Only the part doing the last bind will work.

right, a transaction manager has to be a unique type to be used simultaniously
with another one.
that's a downside of our approach of using a singleton instead of
passing everything, like the active transaction, as function arguments.
and it is the reason why my resource manager implementation has a configurable
tag, so you can do the following:

struct application_preferences_tag;
typedef multiversion_object_resource<...,application_preferences_tag> prefres;
typedef basic_transaction_manager<prefres> preftxmgr;

struct application_data_tag;
typedef multiversion_object_resource<...,application_data_tag> datares;
typedef basic_transaction_manager<datares> datatxmgr;

so preftxmgr::active() yields another result as datatxmgr::active(), and a
basic_transaction<preftxmgr> is completely independent of a
basic_transaction<datatxmgr>.

Regards,
Stefan


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