/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.offheap; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.geode.internal.cache.RegionEntry; /** * All access to this class should be done through the static methods of ReferenceCountHelper. */ class ReferenceCountHelperImpl { private boolean trackRefCounts; private boolean trackFreedRefCounts; private ConcurrentMap<Long, List<RefCountChangeInfo>> stacktraces; private ConcurrentMap<Long, List<RefCountChangeInfo>> freedStacktraces; private ThreadLocal<Object> refCountOwner; private ThreadLocal<AtomicInteger> refCountReenterCount; final static private Object SKIP_REF_COUNT_TRACKING = new Object(); final static private List<RefCountChangeInfo> LOCKED = Collections.emptyList(); ReferenceCountHelperImpl(boolean trackRefCounts, boolean trackFreedRefCounts) { this.trackRefCounts = trackRefCounts; this.trackFreedRefCounts = trackFreedRefCounts; if (trackRefCounts) { stacktraces = new ConcurrentHashMap<Long, List<RefCountChangeInfo>>(); if (trackFreedRefCounts) { freedStacktraces = new ConcurrentHashMap<Long, List<RefCountChangeInfo>>(); } else { freedStacktraces = null; } refCountOwner = new ThreadLocal<Object>(); refCountReenterCount = new ThreadLocal<AtomicInteger>(); } else { stacktraces = null; freedStacktraces = null; refCountOwner = null; refCountReenterCount = null; } } /** * Returns true if reference count tracking is enabled. */ public boolean trackReferenceCounts() { return trackRefCounts; } /** * Returns true if free operation tracking is enabled. */ public boolean trackFreedReferenceCounts() { return trackFreedRefCounts; } /** * Optional call to tell the tracker the logical "owner" of the reference count. For example you * could set the particular EntryEventImpl instance that incremented the reference count and is * responsible for decrementing it. Calling this method is a noop if !trackReferenceCounts. */ public void setReferenceCountOwner(Object owner) { if (trackReferenceCounts()) { if (refCountOwner.get() != null) { AtomicInteger ai = refCountReenterCount.get(); if (owner != null) { ai.incrementAndGet(); } else { if (ai.decrementAndGet() <= 0) { refCountOwner.set(null); ai.set(0); } } } else { AtomicInteger ai = refCountReenterCount.get(); if (ai == null) { ai = new AtomicInteger(0); refCountReenterCount.set(ai); } if (owner != null) { ai.set(1); } else { ai.set(0); } refCountOwner.set(owner); } } } /** * Create, set, and return a generic reference count owner object. Calling this method is a noop * and returns null if !trackReferenceCounts. */ public Object createReferenceCountOwner() { Object result = null; if (trackReferenceCounts()) { result = new Object(); setReferenceCountOwner(result); } return result; } /** * Call this method before incrementing a reference count if you know that tracking is not needed * because you know that the allocate and free will always be done in the same code block. Callers * of this method must also call unskipRefCountTracking after the allocation or free is done. */ public void skipRefCountTracking() { setReferenceCountOwner(SKIP_REF_COUNT_TRACKING); } /** * Returns true if currently tracking reference counts. */ public boolean isRefCountTracking() { if (!trackReferenceCounts()) return false; return !(getReferenceCountOwner() == SKIP_REF_COUNT_TRACKING); } /** * Call this method to undo a call to skipRefCountTracking. */ public void unskipRefCountTracking() { setReferenceCountOwner(null); } /** * Returns a list of any reference count tracking information for the given Chunk address. */ public List<RefCountChangeInfo> getRefCountInfo(long address) { if (!trackReferenceCounts()) return null; List<RefCountChangeInfo> result = stacktraces.get(address); getReferenceCountInfoTestHook(stacktraces, address); while (result != null && !stacktraces.replace(address, result, LOCKED)) { result = stacktraces.get(address); } return result; } /* * This method is overridden during testing to allow simulation of a concurrent update occurring * between stacktraces.get and stacktraces.replace */ protected void getReferenceCountInfoTestHook( ConcurrentMap<Long, List<RefCountChangeInfo>> stacktraces, long address) {} /** * Returns a list of any reference count tracking information for the given Chunk address without * locking. */ public List<RefCountChangeInfo> peekRefCountInfo(long address) { if (!trackReferenceCounts()) return null; return stacktraces.get(address); } /** * Used internally to report that a reference count has changed. */ void refCountChanged(Long address, boolean decRefCount, int rc) { if (!trackReferenceCounts()) return; final Object owner = refCountOwner.get(); if (owner == SKIP_REF_COUNT_TRACKING) { return; } List<RefCountChangeInfo> list = stacktraces.get(address); if (list == null) { List<RefCountChangeInfo> newList = new ArrayList<RefCountChangeInfo>(); refCountChangedTestHook(address, decRefCount, rc); List<RefCountChangeInfo> old = stacktraces.putIfAbsent(address, newList); if (old == null) { list = newList; } else { list = old; } } if (decRefCount) { if (owner != null) { synchronized (list) { for (int i = 0; i < list.size(); i++) { RefCountChangeInfo info = list.get(i); if (owner instanceof RegionEntry) { if (owner == info.getOwner()) { if (info.getUseCount() > 0) { info.decUseCount(); } else { list.remove(i); } return; } } else if (owner.equals(info.getOwner())) { if (info.getUseCount() > 0) { info.decUseCount(); } else { list.remove(i); } return; } } } } } if (list == LOCKED) { MemoryAllocatorImpl.debugLog("refCount " + (decRefCount ? "deced" : "inced") + " after orphan detected for @" + Long.toHexString(address), true); return; } RefCountChangeInfo info = new RefCountChangeInfo(decRefCount, rc, owner); synchronized (list) { // if (list.size() == 16) { // debugLog("dumping @" + Long.toHexString(address) + " history=" + list, false); // list.clear(); // } for (RefCountChangeInfo e : list) { if (e.isSameCaller(info)) { // No need to add it just increment useCount e.incUseCount(); return; } } list.add(info); } } /* * This method is overridden during testing to allow simulation of a race to be the first to * reference a given address */ protected void refCountChangedTestHook(Long address, boolean decRefCount, int rc) {} /** * Called internally when free operations are tracked to record that a free has happened of the * given address. */ void freeRefCountInfo(Long address) { if (!trackReferenceCounts()) return; List<RefCountChangeInfo> freedInfo = stacktraces.remove(address); if (freedInfo == LOCKED) { MemoryAllocatorImpl.debugLog("freed after orphan detected for @" + Long.toHexString(address), true); } else if (trackFreedReferenceCounts()) { if (freedInfo != null) { freedStacktraces.put(address, freedInfo); } else { freedStacktraces.remove(address); } } } /** * Returns the thread local owner */ Object getReferenceCountOwner() { if (!trackReferenceCounts()) return null; return refCountOwner.get(); } /** * Returns the thread local count of the number of times ref count has been updated */ AtomicInteger getReenterCount() { if (!trackReferenceCounts()) return null; return refCountReenterCount.get(); } /** * Returns a list of any free operation tracking information. This is used to describe who did the * previous free(s) when an extra one ends up being done and fails. */ public List<RefCountChangeInfo> getFreeRefCountInfo(long address) { if (!trackReferenceCounts() || !trackFreedReferenceCounts()) return null; return freedStacktraces.get(address); } }