Hi,
marc fleury wrote:
>
> |What I am talking about here is the import of the
> |transaction into the TM. When someone invokes
> |MI.getTransaction() it indicates an interest that
> |the transaction should be used in the local VM. So
> |an import will be needed, so why not just do the
> |import on the first call to MI.getTransaction().
> |This way we do not have to worry about having a
> |Transaction instance that might not have been
> |imported.
>
> I am not sure I understand what you mean by "import", can you be more
> precise in "before/after" terms.
You are right, I should define import and export.
But first I should define an acronym:
TCP := Transaction Propagation Context
Import:
Import happens when a invocation request comes in,
before the transaction of the invocation is used.
When a request comes in we just have a stream of
bytes describing the invocation request. The tx
context (or TransactionImpl) is just part of this
stream of bytes. When deserializing we get a TPC
(not yet) or a TransactionImpl, but these objects
hold no references to VM-local instances.
For the TransactionImpl passing, a reference from
the TransactionImpl to a VM-local instance has always
been needed:
In TransactionImpl revision 1.6 (before I had my hands
on it), TransactionImpl needed a reference to the
TxManager. This reference was set implicitly in
TxManager.associateThread(), and this was the import
of the TransactionImpl in revision 1.6.
In the current revision 1.10 the TransactionImpl no
longer needs a reference to the TxManager, as it
no longer delegates work to TxManager. Instead it
delegates directly to TxCapsule. But that means that
a reference in TransactionImpl to the TxCapsule that
represents the transaction is needed instead. Setting
this TxCapsule reference is what I call "import" of
the Transaction. It is still done implicitly when
TxManager.associateThread() is called.
If a TransactionPropagationContext (TPC) is passed
instead, this TPC will need to be converted to a
Transaction before it is useful. This is what I call
import of the transaction.
Export:
Export happens when calling out with a transactional
context.
For the TransactionImpl passing, no special export
is needed: The TxCapsule reference (or TxManager
reference for rev. 1.6) is simply not put on the
stream because it is declared transient.
For TPC passing we have a Transaction, and the export
involves translating this to a suitable TPC for
putting on the stream.
So import and export is simply the processes of
converting between the internal representation of
transactions and the on-the-wire representation
of transactions (but not including the serialization
and deserialization).
This is a little more explicit in CORBA, though
hidden from the normal user. Unfortunately it is
complicated (a lot) by the support for DII and
deferred invocations, so I am not going to describe
it here.
> |Only the transaction import (which is orthogonal
> |to the thread association) is needed before
> |the transaction can be used.
>
> again please explain, do you mean wired to the Transaction Manager? what do
> you understand by "wired", if is the "tracking" of the Tx by the TM, again
> remember that is a "feature" you introduced recently...
I mean the tracking of the TxCapsule reference in
the TransactionImpl that I recently implemented.
Before I did that (in TransactionImpl rev. 1.6) it
was the tracking of the TxManager reference.
> the original
> "4hours" design was meant as a light and super fast implementation...
> keeping track of all the public faces is expensive imho.
Yes. The problem I see with using a TransactionImpl
for passing the tx context is that we get an extra
copy of it each time the RemoteMethodInvocation is
deserialized.
If instead we pass a TPC and make the import more
explicit (can still hide import in the MI), the
import operation could instead just return a reference
to the single TransactionImpl instance.
> |I agree that the thread association should be up to
> |the container. But transaction import and transaction
> |export are closely tied to the transport used and
> |might better be transparent to the container.
>
> ok explain transaction "import" since it is standalone, the ID of the
> transaction is sufficient to "do work" with it... I don't need an extra step
> for "import" in the current inVM implementation, even after travelling.
> Please be explicit as to what the "import" buys us in your mind that is not
> already there.
The import has always been there, one way or another.
It has always been hidden in TxManager.associateThread().
In the old code it was very fast: The TxManager field
was set and that was it.
After I changed it, it became a bit slower: Now a hash
lookup has to be done to get the TxCapsule reference.
But I think it is worth it: Now we save the extra
redirection of all Transaction invocations over the
TxManager, and the hash lookup in the TxManager that
we had on all these calls. (Even the simple
TransactionImpl.getStatus() involved a hash lookup in
TxManager on every invocation.)
> |I like this fast in-VM TM, and don't think it should
> |be touched or removed even if we get a full DTM.
> |
> |But I would like the interface to the in-VM TM to be
> |so close to the interface of a full-blown DTM that no
> |container code should be changed if/when someone comes
> |up with such a beast. And I think that we are very
>
> agreed,
>
> |very close. Only thing I see missing is better handling
> |of transaction import and export, and this should really
> |be done in cooperation with the transport.
>
> ok be explicit, show you are onto something...
I'll try. My problem with not being explicit is that
I am not really sure what I am onto...
> I am not religious in interfaces and names, as you know I believe in
> iterative design. One thing I don't believe in is complicating things with
> 20 classes and inheritence for the sake of "object design" that imho is
> bull... show me an advantage for your DTM/standalone and you have my
> blessings.
>
> Make it work, it the most important thing in my eyes... that's the only real
> beauty.
I have only focused on the transaction import. The transaction
export is (at least) as important to consider.
Transaction export from the client-side to jBoss over RMI
is (if I read the code correctly) only supported if the
client-side is within a jBoss container.
This is one thing that should change when we get a full
distributed transaction manager.
Here we also have to consider if we want the client-side
to be light-weight or heavy-weight. I think it should be
light-weight.
Now, consider a future jBoss client running in a server of
the dark force. This dark force server is using CORBA OTS
for transaction handling while the jBoss client is using
RMI for calling the jBoss server. If we require that the
transaction propagation context of the RemoteMethodInvocation
must be the specific transaction propagation context of
the DTM we are going to implement, we have to do the
conversion to the jBoss DTM transaction propagation context
format in the client, and that would require an instance of
the jBoss DTM running at client-side.
I do not like this, as it would make the client-side
heavy-weight. It would be better to just send the CORBA OTS
transaction propagation context in the RemoteMethodInvocation
to the jBoss server, and let the jBoss DTM in the server
take care of the conversion.
Getting explicit now:
I would like to implement a new interface for the client-side:
interface TransactionPropagationContextFactory
{
public Object getTransactionPropagationContext();
}
Implementations of this interface are used for getting
a TPC at the client-side. We need a specific implementation
of this interface for each kind of DTM we are going to
interoperate with. (So we may have 20 new classes if we
are going to interoperate with 20 different kinds of
distributed transaction managers.) The reason for having
the methods in this interface return Object is that we
do not really know what kind of TPC we get.
To mirror the current behaviour in a stand-alone jBoss
client we use an implementation that always returns null,
because we cannot export a transaction from the client
(as it is now, that will change when we get a full DTM).
To mirror the current behaviour of a jBoss client in a
jBoss container, we use an implementation that returns
the Xid data of the current transaction (if any) of
the TM of the container.
If/when we get interoperability with CORBA OTS, we use
an implementation that returns an instance of
org.omg.CosTransactions.PropagationContext.
The class org.jboss.ejb.plugins.jrmp.interfaces.GenericProxy
will be changed to have a static variable holding a
reference to a TransactionPropagationContextFactory,
which should default to the one that always returns
null. A class method to set the factory should be added
and used in a jBoss client running in a jBoss container.
Later we could add a configuration setting for this factory
so that users can specify which kind of transaction
environment the jBoss client is running in.
The old "static TransactionManager tm" variable and the
setTransactionManager() class method goes away.
org.jboss.ejb.plugins.jrmp.interfaces.RemoteMethodInvocation
is changed so that the type of the tx instance variable
is Object, as we do not know what kind of reference we pass
here. Only requirement is that it should be Serializable.
Subclasses of GenericProxy are changed too: For optimized
local calls we use the raw Transaction instance for passing
to container.invoke(), and for remote calls we call the
TPC factory of the GenericProxy to obtain a reference to
the transaction propagation context.
That was client-side, RMI transport and transaction
export.
For the server-side and transaction import we do as
follows:
The class org.jboss.tm.TxManager gets a new method for
explicitly importing a transaction:
public Transaction importTPC(Object tpc);
How this works depends on the type of the argument:
If tpc is null (no transaction) null is returned.
If tpc is a TransactionImpl instance, tpc is simply
returned, as we know the invocation is VM-local (It
might be wise to make TransactionImpl non-Serializable
so that somebody in another VM cannot write a bad TPC
factory to send us this).
If tpc is a instance of the class we create to
propagate in-VM transactions, we import and return
the single TransactionImpl instance for the transaction.
Otherwise we throw an exception that should propagate
back to the client to indicate that this TCP is unknown.
Class org.jboss.ejb.MethodInvocation is changed so
that the tx instance variable is private; we can still
use the getter and setter for accessing this variable.
The constructor is changed so that the tx argument is
replaced by a tpc argument of type Object. A new
private instance variable tpc is added to hold the TPC.
The getTransaction() method is changed to something
like:
public Transaction getTransaction()
{
if (tx == null && tpc != null)
tx = getTM().importTPC(tpc);
return tx;
}
This means that the transaction will automagically
be imported the first time it is accessed, and that
it will _not_ be imported if never used (BMT, for
example).
There is a minor problem here: We hold no reference
to the transaction manager of the container here, so
how can I get to the right TM instead of using a pseudo
getTM() method? Should we add an instance variable that
refers to the container and include that data in the
MI constructor?
I guess this roughly outlines what I am up to.
Advantages of this are:
- Only a single TransactionImpl instance per in-VM
transaction.
- Transaction import is really null (calling a
method that just returns null) for optimized local
invocations.
- TxManager.associateThread() no longer needs to be
called before the Transaction can be used, making
it up to container programmers to decide if/when
the transaction should be associated with a thread.
- Ready for a full DTM without protocol changes or
code changes outside the TM.
- Client-side remains light-weight even when our DTM
supports importing transactions from other types
of transaction managers.
Unless someone shouts NO or have ideas for how this
could be done better, I am going to try to implement
it.
Best Regards,
Ole Husgaard.