@Override public Xid[] recover(int flags) throws XAException { if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMSTARTRSCAN && flags != XAResource.TMENDRSCAN && flags != (XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN)) { throw new EhcacheXAException("Recover flags not supported : " + xaResourceFlagsToString(flags), XAException.XAER_INVAL); } if ((flags & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN) { List<Xid> xids = new ArrayList<>(); Set<TransactionId> transactionIds = journal.recover().keySet(); for (TransactionId transactionId : transactionIds) { // filter-out in-flight tx if (!transactionContextFactory.contains(transactionId)) { xids.add(transactionId.getSerializableXid()); } } return xids.toArray(new Xid[xids.size()]); } return new Xid[0]; }
@Override public void start(Xid xid, int flag) throws XAException { if (flag != XAResource.TMNOFLAGS && flag != XAResource.TMJOIN) { throw new EhcacheXAException("Start flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL); } if (currentXid != null) { throw new EhcacheXAException("Already started on : " + xid, XAException.XAER_PROTO); } TransactionId transactionId = new TransactionId(xid); XATransactionContext<K, V> transactionContext = transactionContextFactory.get(transactionId); if (flag == XAResource.TMNOFLAGS) { if (transactionContext == null) { transactionContext = transactionContextFactory.createTransactionContext(transactionId, underlyingStore, journal, transactionTimeoutInSeconds); } else { throw new EhcacheXAException("Cannot start in parallel on two XIDs : starting " + xid, XAException.XAER_RMERR); } } else { if (transactionContext == null) { throw new EhcacheXAException("Cannot join unknown XID : " + xid, XAException.XAER_NOTA); } } if (transactionContext.hasTimedOut()) { transactionContextFactory.destroy(transactionId); throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT); } currentXid = xid; }
public XAStore(Class<K> keyType, Class<V> valueType, Store<K, SoftLock<V>> underlyingStore, TransactionManagerWrapper transactionManagerWrapper, TimeSource timeSource, Journal<K> journal, String uniqueXAResourceId) { super(keyType, valueType, true); this.underlyingStore = underlyingStore; this.transactionManagerWrapper = transactionManagerWrapper; this.timeSource = timeSource; this.journal = journal; this.uniqueXAResourceId = uniqueXAResourceId; this.transactionContextFactory = new XATransactionContextFactory<>(timeSource); this.recoveryXaResource = new EhcacheXAResource<>(underlyingStore, journal, transactionContextFactory); this.eventSourceWrapper = new StoreEventSourceWrapper<>(underlyingStore.getStoreEventSource()); ContextManager.associate(underlyingStore).withParent(this); }
@Override public int prepare(Xid xid) throws XAException { if (currentXid != null) { throw new EhcacheXAException("Cannot prepare a non-ended start on : " + xid, XAException.XAER_PROTO); } TransactionId transactionId = new TransactionId(xid); XATransactionContext<K, V> transactionContext = transactionContextFactory.get(transactionId); if (transactionContext == null) { throw new EhcacheXAException("Cannot prepare unknown XID : " + xid, XAException.XAER_NOTA); } boolean destroyContext = false; try { destroyContext = transactionContext.prepare() == 0; return destroyContext ? XA_RDONLY : XA_OK; } catch (XATransactionContext.TransactionTimeoutException tte) { destroyContext = true; throw new EhcacheXAException("Transaction timed out", XAException.XA_RBTIMEOUT); } catch (IllegalStateException ise) { throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_PROTO, ise); } catch (StoreAccessException cae) { throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_RMERR, cae); } finally { if (destroyContext) { transactionContextFactory.destroy(transactionId); } } }
@Test public void testTimeoutStart() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); when(xaTransactionContext.hasTimedOut()).thenReturn(true); try { xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XA_RBTIMEOUT)); } verify(xaTransactionContextFactory, times(1)).destroy(eq(new TransactionId(new TestXid(0, 0)))); }
@Test public void testStartEndWorks() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); xaResource.end(new TestXid(0, 0), XAResource.TMSUCCESS); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 1))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 1), XAResource.TMNOFLAGS); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 1))))).thenReturn(xaTransactionContext); xaResource.end(new TestXid(0, 1), XAResource.TMSUCCESS); }
public XATransactionContext<K, V> getCurrentContext() { if (currentXid == null) { return null; } return transactionContextFactory.get(new TransactionId(currentXid)); }
@Test public void testTwoNonEndedStartsFails() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(1, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); try { xaResource.start(new TestXid(1, 0), XAResource.TMNOFLAGS); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XAER_PROTO)); } }
@Test public void testRecoveryCommit() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(journal.recover()).thenReturn(Collections.singletonMap(new TransactionId(new TestXid(0, 0)), (Collection<Long>) Arrays.asList(1L, 2L, 3L))); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L, 3L)); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); Xid[] recoveredXids = xaResource.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN); assertThat(recoveredXids.length, is(1)); xaResource.commit(recoveredXids[0], false); verify(xaTransactionContextFactory, times(0)).destroy(eq(new TransactionId(new TestXid(0, 0)))); verify(underlyingStore, times(1)).get(eq(1L)); verify(underlyingStore, times(1)).get(eq(2L)); verify(underlyingStore, times(1)).get(eq(3L)); }
@Override public Iterator<Cache.Entry<K, ValueHolder<V>>> iterator() { XATransactionContext<K, V> currentContext = getCurrentContext(); Map<K, XAValueHolder<V>> valueHolderMap = transactionContextFactory.listPuts(currentContext.getTransactionId()); return new XAIterator(valueHolderMap, underlyingStore.iterator(), currentContext.getTransactionId()); }
@Override public void end(Xid xid, int flag) throws XAException { if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL) { throw new EhcacheXAException("End flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL); } if (currentXid == null) { throw new EhcacheXAException("Not started on : " + xid, XAException.XAER_PROTO); } TransactionId transactionId = new TransactionId(currentXid); XATransactionContext<K, V> transactionContext = transactionContextFactory.get(transactionId); if (transactionContext == null) { throw new EhcacheXAException("Cannot end unknown XID : " + xid, XAException.XAER_NOTA); } boolean destroyContext = false; if (flag == XAResource.TMFAIL) { destroyContext = true; } currentXid = null; try { if (transactionContext.hasTimedOut()) { destroyContext = true; throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT); } } finally { if (destroyContext) { transactionContextFactory.destroy(transactionId); } } }
@Test public void testJoinWorks() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); xaResource.end(new TestXid(0, 0), XAResource.TMSUCCESS); xaResource.start(new TestXid(0, 0), XAResource.TMJOIN); xaResource.end(new TestXid(0, 0), XAResource.TMSUCCESS); }
@Test public void testCannotCommit1PcPreparedXid() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); doThrow(IllegalStateException.class).when(xaTransactionContext).commitInOnePhase(); try { xaResource.commit(new TestXid(0, 0), true); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XAER_PROTO)); } }
@Test public void testCannotRollbackNonEndedXid() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); try { xaResource.rollback(new TestXid(0, 0)); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XAER_PROTO)); } }
@Test public void testRecoveryRollback() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); when(journal.recover()).thenReturn(Collections.singletonMap(new TransactionId(new TestXid(0, 0)), (Collection<Long>) Arrays.asList(1L, 2L, 3L))); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L, 3L)); Xid[] recoveredXids = xaResource.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN); assertThat(recoveredXids.length, is(1)); xaResource.rollback(recoveredXids[0]); verify(xaTransactionContextFactory, times(0)).destroy(eq(new TransactionId(new TestXid(0, 0)))); verify(underlyingStore, times(1)).get(eq(1L)); verify(underlyingStore, times(1)).get(eq(2L)); verify(underlyingStore, times(1)).get(eq(3L)); }
@Override public Iterator<Cache.Entry<K, ValueHolder<V>>> iterator() { XATransactionContext<K, V> currentContext = getCurrentContext(); Map<K, XAValueHolder<V>> valueHolderMap = transactionContextFactory.listPuts(currentContext.getTransactionId()); return new XAIterator(valueHolderMap, underlyingStore.iterator(), currentContext.getTransactionId()); }
@Test public void testPrepareReadOnly() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); when(xaTransactionContext.prepare()).thenReturn(0); int prepareRc = xaResource.prepare(new TestXid(0, 0)); assertThat(prepareRc, is(XAResource.XA_RDONLY)); verify(xaTransactionContextFactory, times(1)).destroy(eq(new TransactionId(new TestXid(0, 0)))); }
@Test public void testTimeoutEndSuccess() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); when(xaTransactionContext.hasTimedOut()).thenReturn(true); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); try { xaResource.end(new TestXid(0, 0), XAResource.TMSUCCESS); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XA_RBTIMEOUT)); } verify(xaTransactionContextFactory, times(1)).destroy(eq(new TransactionId(new TestXid(0, 0)))); }
@Test public void testCannotRollbackUnknownXidInFlight() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.get(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(xaTransactionContext); doThrow(IllegalStateException.class).when(xaTransactionContext).rollback(eq(false)); try { xaResource.rollback(new TestXid(0, 0)); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XAER_NOTA)); } }
@Test public void testCannotPrepareNonEndedXid() throws Exception { EhcacheXAResource<Long, String> xaResource = new EhcacheXAResource<>(underlyingStore, journal, xaTransactionContextFactory); when(xaTransactionContextFactory.createTransactionContext(eq(new TransactionId(new TestXid(0, 0))), refEq(underlyingStore), refEq(journal), anyInt())).thenReturn(xaTransactionContext); xaResource.start(new TestXid(0, 0), XAResource.TMNOFLAGS); try { xaResource.prepare(new TestXid(0, 0)); fail("expected XAException"); } catch (XAException xae) { assertThat(xae.errorCode, is(XAException.XAER_PROTO)); } }