/* * JBoss, Home of Professional Open Source * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2005-2006, * @author JBoss Inc. */ /* * Copyright (C) 2005, * * Arjuna Solutions Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: RecoveryXids.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jta.recovery.arjunacore; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import com.arjuna.ats.arjuna.logging.tsLogger; import com.arjuna.ats.jta.common.jtaPropertyManager; import com.arjuna.ats.jta.xa.XidImple; public class RecoveryXids { public RecoveryXids (XAResource xares) { _xares = xares; _lastValidated = System.currentTimeMillis(); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids new recoveryXids " + xares + " " + _lastValidated); } public boolean equals (Object obj) { if (obj instanceof RecoveryXids) { try { return ((RecoveryXids) obj)._xares.isSameRM(_xares); } catch (Exception ex) { } } return false; } /** * Update our tracking with results of a new recovery scan pass * @param trans the Xids seen during the new scan. */ public final void nextScan (Xid[] trans) { long currentTime = System.currentTimeMillis(); // record the new information: if(trans != null) { for(Xid xid : trans) { XidImple xidImple = new XidImple(xid); if(!_whenFirstSeen.containsKey(xidImple)) { _whenFirstSeen.put(xidImple, currentTime); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids _whenFirstSeen put nextScan " + _xares + " " + currentTime + " === " + xidImple); } _whenLastSeen.put(xidImple, currentTime); } } // garbage collect the stale information: Set<XidImple> candidates = new HashSet<XidImple>(_whenFirstSeen.keySet()); for(XidImple candidate : candidates) { if(_whenLastSeen.get(candidate) != currentTime) { // seen it previously but it's gone now so we can forget it: _whenFirstSeen.remove(candidate); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids _whenFirstSeen remove nextScan" + _xares + " " + currentTime + " === " + candidate); _whenLastSeen.remove(candidate); } } // gc note: transient errors in distributed RMs may cause values to disappear in one scan and then reappear later. // under the current model we'll recover Xids only if they stick around for enough consecutive scans to // span the safely interval. In the unlikely event that causes problems, we'll need to postpone gc for a given // interval and take care to include only Xids seen in the most recent scan when returning candidates for recovery. } /** * Return any Xids that should be considered for recovery. * @return Xids that are old enough to be eligible for recovery. */ public final Xid[] toRecover () { List<Xid> oldEnoughXids = new LinkedList<Xid>(); long currentTime = System.currentTimeMillis(); for(Map.Entry<XidImple,Long> entry : _whenFirstSeen.entrySet()) { if(entry.getValue() + safetyIntervalMillis <= currentTime) { oldEnoughXids.add(entry.getKey()); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids _whenFirstSeen toRecover yes " + _xares + " " + entry.getValue() + " === " + currentTime); } if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids _whenFirstSeen toRecover no " + _xares + " " + entry.getValue() + " === " + currentTime); } return oldEnoughXids.toArray(new Xid[oldEnoughXids.size()]); } public final boolean isSameRM (XAResource xares) { try { if (xares == null) return false; else return xares.isSameRM(_xares); } catch (Exception ex) { return false; } } public boolean contains (Xid xid) { XidImple xidImple = new XidImple(xid); return _whenFirstSeen.containsKey(xidImple); } // JBTM-924 public boolean isStale() { long now = System.currentTimeMillis(); // JBTM-1255 - use a different safety declaration for staleness, if you set a safety interval of 0 (using reflection) then // you will detect everything as stale. The only time we actually set safetyIntervalMillis is in JBTM-895 unit test SimpleIsolatedServers // so in the normal case this will behave as before long threshold = _lastValidated+(2*safetyIntervalMillis < staleSafetyIntervalMillis ? staleSafetyIntervalMillis : 2*safetyIntervalMillis); long diff = now - threshold; boolean result = diff > 0; if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids isStale Check " + _xares + " " + _lastValidated + " " + now + " " + result); return result; } public boolean remove (Xid xid) { XidImple xidImple = new XidImple(xid); if (_whenFirstSeen.containsKey(xidImple)) { _whenFirstSeen.remove(xidImple); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids _whenFirstSeen remove remove " + _xares + " " + _lastValidated + " " + xidImple); _whenLastSeen.remove(xidImple); return true; } else { return false; } } public boolean isEmpty() { return _whenFirstSeen.isEmpty(); } /** * If supplied xids contains any values seen on prev scans, replace the existing * XAResource with the supplied one and return true. Otherwise, return false. * * @param xaResource * @param xids * @return true if equivalent */ public boolean updateIfEquivalentRM(XAResource xaResource, Xid[] xids) { if(xids != null && xids.length > 0) { for(int i = 0; i < xids.length; i++) { if(contains(xids[i])) { _xares = xaResource; _lastValidated = System.currentTimeMillis(); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids updateIfEquivalentRM1 " + _xares + " " + _lastValidated); return true; } } } // either (or both) passes have an empty Xid set, // so fallback to isSameRM as we can't use Xid matching if(isSameRM(xaResource)) { _xares = xaResource; _lastValidated = System.currentTimeMillis(); if (tsLogger.logger.isTraceEnabled()) tsLogger.logger.trace("RecoveryXids updateIfEquivalentRM2 " + _xares + " " + _lastValidated); return true; } return false; } public int size() { return _whenFirstSeen.size(); } // record when we first saw and most recently saw a given Xid. time in system clock (milliseconds). // since we don't trust 3rd party hashcode/equals we convert to our own wrapper class. private final Map<XidImple,Long> _whenFirstSeen = new HashMap<XidImple, Long>(); private final Map<XidImple,Long> _whenLastSeen = new HashMap<XidImple, Long>(); private XAResource _xares; private long _lastValidated; /** * JBTM-1255 this is required to reinstate JBTM-924, see message in @see RecoveryXids#isStale() */ private static final int staleSafetyIntervalMillis; // The advice is that this (if made configurable is twice the safety interval) // JBTM-916 removed final so 10000 is not inlined into source code until we make this configurable // https://issues.jboss.org/browse/JBTM-842 private static int safetyIntervalMillis; // may eventually want to make this configurable? static { safetyIntervalMillis = jtaPropertyManager.getJTAEnvironmentBean().getOrphanSafetyInterval(); if (safetyIntervalMillis > 0) { staleSafetyIntervalMillis = safetyIntervalMillis * 2; } else { staleSafetyIntervalMillis = 20000; } } }