Boost logo

Boost :

Subject: Re: [boost] [transaction] New Boost.Transaction libraryunderdiscussion
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-01-20 18:49:25


Hi, sorry tp replay so late.
----- Original Message -----
From: "Stefan Strasser" <strasser_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Tuesday, January 19, 2010 12:10 AM
Subject: Re: [boost] [transaction] New Boost.Transaction libraryunderdiscussion

>
> Am Monday 18 January 2010 21:52:49 schrieb vicente.botet:
>> Looking at the code I think that the lazy creation of resource associated
>> to the created transaction introduce more problems that it 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?
>
> why couldn't you ensure that? line 170 recursively calls
> resource_transaction() to do exactly that.

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. Is for this reason I said that it is better to ensure that all the resources have the same stack of nested transactions.

 
>> > basic_transaction_manager.hpp: my implementation of the
>> > TransactionManager concept
>>
>> I find extrange the way basic_transaction_manager is made a singleton.
>>
>> static bool has_active(){ return active_; }
>>
>> static basic_transaction_manager &active(){
>> if(active_) return *active_;
>> else persistent::detail::throw_(no_active_transaction_manager());
>> }
>> void bind(){ active_=this; }
>> static void unbind(){ active_=0; }
>>
>> What can the application do when there is no transaction_manager? Nothing
>> in my opinion, so the system need to ensure this invariant. I would preffer
>> just an instance() static function.
>
> correct, Nothing. and it is a singleton.
> but it is not invariant.
>
> 1. the transaction manager is constructed by the user, and it must be. the
> entire runtime configuration of a library depends on that, including the
> filename of a database file, desired cache size, etc.
> (by passing those to a resource manager and then passing the resource manager
> to the transaction manager constructor).
>
> 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(); }
    ...
}
 
> 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.
 
>
>> As the active transaction can be null the better is that the function
>> return the pointer to the active transaction. I find this prototype simple.
>>
>> transaction* active_transaction() const{
>> return this->active_transaction(mpl::bool_<Threads>());
>> }
>
> this is not uber-important to me and returning a pointer would be ok, but I
> nevertheless disagree with that.
>
> you are looking at the implementation of basic_transaction_manager.
> I was foremost trying to define a concept TransactionManager without looking
> at implementation details.

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 documentation we were not able to have this discussion. I will let the implementation details for the moment and concentrate on the concepts from now on, but I will however replay to your comments.
  
> 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?
 
> 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 .
 
>> With this interface the functions are clearer. Instead of
>>
>> typename detail::transaction_construct_t begin_transaction(){
>> if(this->has_active_transaction()) return typename
>> detail::transaction_construct_t(&this->active_transaction()); else return
>> typename detail::transaction_construct_t(0);
>> }
>
> implementation details.

Sure.
 
>>
>> > basic_transaction.hpp: the transaction scope class, and the
>> > atomic{}retry; macros.
>>
>> I would separate the language-like macros from the basic_transaction class.
>
> into a seperate header? ok.
> I'd also prefer another for the macro itself, considering boost::atomic...
>
>>
>> > transaction_manager.hpp: the (configurable) default transaction manager
>> >
>> > basic_loc.hpp and loc.hpp: an example of a C++98 pseudo-template-alias.
>> > note the anonymous namespace and the conversion operators in loc.hpp. I
>> > think this technically violates the One Definition Rule but I don't think
>> > this actually causes a problem.
>>
>> If I have understood it will be only one transaction_manager class for an
>> application. Why add the template parameter TxMgr to all the other
>> classes? In other words, which class other than transaction_manager can be
>> a template for the basic_loc class?
>>
>> template<class T>
>> class loc : public basic_loc<T,transaction_manager>{...}
>> template<class T>
>> class loc2 : public basic_loc<T,???>{...}
>
> 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?
 
> 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? That we can have another stm::transaction_manager and possible also a third persistent_and_stm::transaction_manager, and that the developer will choose the transaction_manager depending on the context? If this was your goal, I understand now why you have the parameter TxMgr.

While I can understand the independence need I don't see why what I would consider as a resource manager takes the place of a transaction manager. Parts of the application that uses a single resource manager will not conflict with other parts of the application that uses other resource managers as far as the resources managers don't manage the same resource. The following declaration

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

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.

Even you can have
typedef basic_transaction_manager<vector<persistent::resource_manager1, persistent::resource_manager2>>. transaction_manager;

 
>>
>> template<class TxMgr>
>> class basic_transaction;
>> typedef basic_transaction<transaction_manager> transaction;
>>
>> we can have just
>>
>> class transaction; // using the single transaction_manager
>
> ...for the entire application, possibly including linked libraries.

Ah, I see the real problem. If you want dynamic libraries participate on a transaction in an independent way I don't see how to achieve that if we don't have a *single* transaction_manager with dynamic polymorphic resource managers, and even more a transaction with dynamic polymorphic resources.

>From my side, the goal is to have a single transactional system, with no more than a transaction by thread. I don't see how independent transactions can be nested without adding a lot of truble in the application. I don't say that this is not possible, just that this will need some kind of synchronization. How your design behaves in this context? Could you clarify on which context your design allowing multiple transaction managers works? Which parts of the application can use which transaction manager, can resources (objects) be shared by transactional managers, ...? Should these multiple transaction managers ne used uniquely by completly independent parts of the application running is separated threads and accessing completly separated data?

I think I start to see the limits of having a static list of resources managers associated to a transaction_manager and the high risk to have multiple transaction managers.

Hoping the discussion was not limited to implementation details and thatwe will find a design that covers the different expectations.

Regards,
Vicente


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