Subject Recover from prepared transaction (limbo transaction in 2 Phase Commit)
Author Josef Gschwendtner
Hi,

we have an app which swaps data from one database to another and
therefore we need a transaction across two databases.
We use FBConnectionPoolDataSource to connect to the databases. Doing
this we use a XAConnection and a XAResource to realize a 2 phase commit
protocol (without using a transaction manager like JOTM)

If there is an error between prepare and commit (maybe because of a
power loss), we get limbo transactions. With GFix one can solve these
limbo transactions, but what we like to do is solve the limbo
transactions from within our java application. Is this possible?

If we detect the Xid's of the limbo transaction (from the database) then
we cann't do a rollback or a commit for these Xid's. An exception occurs
on getXaRes().rollback(xid).
How can we establish a connection to a limbo transaction?

>>> Doing these tests we think we found a bug within the implementation
of FBXid (jaybird 1.5.5):
Originally we wanted to use the method recover of XAResource
(FBManagedConnection). It should return an array of all limbo
transaction Xid's. But this method always brings a OutOfMemory
exception. The reason is the implementation of FBXid. On serialize
(methode toBytes()) the length of globalId is stored as byte.
On deserialize (constructor of FBXid) globalID is read by method
readBuffer (XdrInputStream). Within this method the length of the buffer
which is read is declared as integer (4 byte).

Therefore an OutOfMemoryError exception occurs.


FBXId.java

FBXid(InputStream rawIn) throws ResourceException
{
try
{
XdrInputStream in = new XdrInputStream(rawIn);

if (in.read() != TDR_VERSION)
{
throw new FBResourceException("Wrong TDR_VERSION for
xid");
}
...
...

globalId = in.readBuffer(); //--> leads to an Exception
if (in.read() != TDR_XID_BRANCH_ID)
{
throw new FBResourceException("Wrong
TDR_XID_BRANCH_ID for xid");
}
branchId = in.readBuffer();
}


byte[] toBytes() {
byte[] b = new byte[getLength()];
int i = 0;
b[i++] = (byte)TDR_VERSION;
b[i++] = (byte)TDR_XID_FORMAT_ID;
b[i++] = (byte)((formatId >>> 24) & 0xff);
b[i++] = (byte)((formatId >>> 16) & 0xff);
b[i++] = (byte)((formatId >>> 8) & 0xff);
b[i++] = (byte)((formatId >>> 0) & 0xff);
b[i++] = (byte)TDR_XID_GLOBAL_ID;
b[i++] = (byte)globalId.length; // --> globalId length is
saved in one byte
System.arraycopy(globalId, 0, b, i, globalId.length);
i += globalId.length;
b[i++] = (byte)TDR_XID_BRANCH_ID;
b[i++] = (byte)branchId.length;
System.arraycopy(branchId, 0, b, i, branchId.length);
return b;
}


XdrInputStream

public byte[] readBuffer() throws IOException {
int len = readInt();// --> length is read as integer (4
byte)
byte[] buffer = new byte[len];
readFully(buffer,0,len);
readFully(pad,0,(4 - len) & 3);
return buffer;

Regards,
Josef