
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