blob: 5671077f911f73708327d77ffb1acc00547ae53b [file] [log] [blame]
/*
* 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.annotations.internal.MakeNotStatic;
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;
private static final Object SKIP_REF_COUNT_TRACKING = new Object();
@MakeNotStatic
private static final 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);
}
}