/** * Copyright (c) 2002-2013 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.android.service; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantLock; import org.neo4j.graphdb.Transaction; import org.neo4j.javax.transaction.TransactionManager; import org.neo4j.kernel.TopLevelTransaction; import android.os.IBinder; import android.util.Log; /** * A helper that associates Neo4j transactions with IBinder instances. */ public class TrxManager { private static final String TAG = TrxManager.class.getSimpleName(); private class TrxHolder { private Transaction trx; private TransactionManager jtaTxManager; private long lastModifiedTime; private long totalOperationTime; private StringBuffer opTimes = new StringBuffer(); public TrxHolder(Transaction trx, TransactionManager jtaTrxMgr) { this.trx = trx; this.jtaTxManager = jtaTrxMgr; this.lastModifiedTime = System.currentTimeMillis(); } public void startProfiling() { lastModifiedTime = System.currentTimeMillis(); } public void suspendProfiling(String operationName) { long operationTime = System.currentTimeMillis() - lastModifiedTime; totalOperationTime += operationTime; opTimes.append(operationName); opTimes.append(":"); opTimes.append(operationTime); opTimes.append("ms,"); } public String getProfilingResult() { opTimes.append("total:"); opTimes.append(totalOperationTime); opTimes.append("ms"); return opTimes.toString(); } public long getLastModifiedTime() { return lastModifiedTime; } } private ReentrantLock mLock; /** * Use Binder identity instead of Thread identity for transaction context */ private HashMap<IBinder, TrxHolder> mTrxMap; public TrxManager() { mTrxMap = new HashMap<IBinder, TrxHolder>(); mLock = new ReentrantLock(true); } public Transaction getAssociatedTrx(IBinder binder) { try { mLock.lock(); TrxHolder holder = mTrxMap.get(binder); if (holder == null) { return null; } else { holder.startProfiling(); return holder.trx; } } finally { mLock.unlock(); } } public void suspendCurrentTrx(IBinder binder, String operationName) { try { mLock.lock(); TrxHolder holder = mTrxMap.get(binder); if (holder != null) { holder.suspendProfiling(operationName); } } finally { mLock.unlock(); } } public Transaction disassociateTrx(IBinder binder, String operationName) { try { mLock.lock(); TrxHolder holder = mTrxMap.remove(binder); if (holder == null) { Log.w(TAG, "Attempted to disassociate transaction from Binder, but none found"); return null; } else { holder.suspendProfiling(operationName); Log.d(TAG, "Disassociated transaction, profiling: " + holder.getProfilingResult()); return holder.trx; } } finally { mLock.unlock(); } } public void associateTrx(IBinder binder, Transaction trx, TransactionManager trxMgr) { try { mLock.lock(); if (hasAssociatedTrx(binder)) { throw new IllegalStateException("A transaction is already associated with this Binder"); } TrxHolder holder = new TrxHolder(trx, trxMgr); holder.startProfiling(); // association at beginning of trx mTrxMap.put(binder, holder); } finally { mLock.unlock(); } } public boolean hasAssociatedTrx(IBinder binder) { try { mLock.lock(); return mTrxMap.containsKey(binder); } finally { mLock.unlock(); } } /* package */int rollbackZombies(long periodMillis) { int numCleaned = 0; try { mLock.lock(); final long now = System.currentTimeMillis(); Iterator<Entry<IBinder, TrxHolder>> entries = mTrxMap.entrySet().iterator(); while (entries.hasNext()) { Entry<IBinder, TrxHolder> entry = entries.next(); if (now > entry.getValue().getLastModifiedTime() + periodMillis) { entries.remove(); numCleaned++; Log.w(TAG, "Reaping zombie (?) transaction after " + (now - entry.getValue().getLastModifiedTime()) / 1000 + "s"); try { Transaction trx = entry.getValue().trx; TransactionManager trxMgr = entry.getValue().jtaTxManager; // resume the transaction on the reaper thread Log.d(TAG, "Resuming transaction on reaper thread"); trxMgr.resume(((TopLevelTransaction) trx).getJtaTransaction()); // mark it as failed, and finish it (that will // disassociate it with the thread) trx.failure(); trx.finish(); } catch (Exception ex) { Log.e(TAG, "Failed to clean up zombie transaction", ex); } } } } finally { mLock.unlock(); } return numCleaned; } }