mirror of https://github.com/apache/cassandra
Prevent InaccessibleObjectException when the Leak Detector is traversing objects
patch by Ekaterina Dimitrova, reviewed by Benjamin Lerer for CASSANDRA-18708
This commit is contained in:
parent
f8d4e2a3c8
commit
000533cb63
|
|
@ -1,4 +1,5 @@
|
|||
5.0
|
||||
* Prevent InaccessibleObjectException when the Leak Detector is traversing objects (CASSANDRA-18708)
|
||||
* Remove legacy command line options from cassandra-stress (CASSANDRA-18529)
|
||||
* Remove commitlog_sync_batch_window_in_ms (CASSANDRA-17161)
|
||||
* Upgrade JMH from 1.21 to 1.36 (CASSANDRA-18696)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.cassandra.exceptions;
|
||||
|
||||
public class UnaccessibleFieldException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 7340063332562337064L;
|
||||
|
||||
public UnaccessibleFieldException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnaccessibleFieldException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,8 @@ import java.util.concurrent.*;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
import org.apache.cassandra.exceptions.UnaccessibleFieldException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -49,6 +51,7 @@ import org.apache.cassandra.utils.ExecutorUtils;
|
|||
import org.apache.cassandra.utils.NoSpamLogger;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.Shared;
|
||||
import sun.misc.Unsafe;
|
||||
import sun.nio.ch.DirectBuffer;
|
||||
|
||||
import org.cliffc.high_scale_lib.NonBlockingHashMap;
|
||||
|
|
@ -73,10 +76,10 @@ import static org.apache.cassandra.utils.Throwables.merge;
|
|||
* - encapsulates a Ref, we'll call selfRef, to which it proxies all calls to RefCounted behaviours
|
||||
* - users must ensure no references to the selfRef leak, or are retained outside of a method scope.
|
||||
* (to ensure the selfRef is collected with the object, so that leaks may be detected and corrected)
|
||||
*
|
||||
* <p>
|
||||
* This class' functionality is achieved by what may look at first glance like a complex web of references,
|
||||
* but boils down to:
|
||||
*
|
||||
* <p>
|
||||
* {@code
|
||||
* Target --> selfRef --> [Ref.State] <--> Ref.GlobalState --> Tidy
|
||||
* ^
|
||||
|
|
@ -86,10 +89,10 @@ import static org.apache.cassandra.utils.Throwables.merge;
|
|||
* Global -------------------------------------
|
||||
* }
|
||||
* So that, if Target is collected, Impl is collected and, hence, so is selfRef.
|
||||
*
|
||||
* <p>
|
||||
* Once ref or selfRef are collected, the paired Ref.State's release method is called, which if it had
|
||||
* not already been called will update Ref.GlobalState and log an error.
|
||||
*
|
||||
* <p>
|
||||
* Once the Ref.GlobalState has been completely released, the Tidy method is called and it removes the global reference
|
||||
* to itself so it may also be collected.
|
||||
*/
|
||||
|
|
@ -400,7 +403,7 @@ public final class Ref<T> implements RefCounted<T>
|
|||
}
|
||||
}
|
||||
|
||||
static final Deque<InProgressVisit> inProgressVisitPool = new ArrayDeque<InProgressVisit>();
|
||||
static final Deque<InProgressVisit> inProgressVisitPool = new ArrayDeque<>();
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
static InProgressVisit newInProgressVisit(Object o, List<Field> fields, Field field, String name)
|
||||
|
|
@ -525,16 +528,16 @@ public final class Ref<T> implements RefCounted<T>
|
|||
if (o instanceof WeakReference & nextField.getDeclaringClass() == Reference.class)
|
||||
continue;
|
||||
|
||||
Object nextObject = nextField.get(o);
|
||||
Object nextObject = getFieldValue(o, nextField);
|
||||
if (nextObject != null)
|
||||
return Pair.create(nextField.get(o), nextField);
|
||||
return Pair.create(getFieldValue(o, nextField), nextField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return field == null ? name : field.toString() + "-" + o.getClass().getName();
|
||||
return field == null ? name : field + "-" + o.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +614,6 @@ public final class Ref<T> implements RefCounted<T>
|
|||
{
|
||||
path.offer(inProgress);
|
||||
inProgress = newInProgressVisit(child, getFields(child.getClass()), field, null);
|
||||
continue;
|
||||
}
|
||||
else if (visiting == child)
|
||||
{
|
||||
|
|
@ -629,7 +631,6 @@ public final class Ref<T> implements RefCounted<T>
|
|||
{
|
||||
returnInProgressVisit(inProgress);
|
||||
inProgress = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
|
|
@ -653,13 +654,68 @@ public final class Ref<T> implements RefCounted<T>
|
|||
{
|
||||
if (field.getType().isPrimitive() || Modifier.isStatic(field.getModifiers()))
|
||||
continue;
|
||||
field.setAccessible(true);
|
||||
fields.add(field);
|
||||
}
|
||||
fields.addAll(getFields(clazz.getSuperclass()));
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unsafe instance used to access object protected by the Module System
|
||||
*/
|
||||
private static final Unsafe unsafe = loadUnsafe();
|
||||
|
||||
private static Unsafe loadUnsafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Unsafe.getUnsafe();
|
||||
}
|
||||
catch (final Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
field.setAccessible(true);
|
||||
return (Unsafe) field.get(null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getFieldValue(Object object, Field field)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This call will unfortunately emit a warning for some scenario (which was a weird decision from the JVM designer)
|
||||
if (field.trySetAccessible())
|
||||
{
|
||||
// The field is accessible lets use reflection.
|
||||
return field.get(object);
|
||||
}
|
||||
|
||||
// The access to the field is being restricted by the module system. Let's try to go around it through Unsafe.
|
||||
if (unsafe == null)
|
||||
throw new UnaccessibleFieldException("The value of the '" + field.getName() + "' field from " + object.getClass().getName()
|
||||
+ " cannot be retrieved as the field cannot be made accessible and Unsafe is unavailable");
|
||||
|
||||
long offset = unsafe.objectFieldOffset(field);
|
||||
|
||||
boolean isFinal = Modifier.isFinal(field.getModifiers());
|
||||
boolean isVolatile = Modifier.isVolatile(field.getModifiers());
|
||||
|
||||
return isFinal || isVolatile ? unsafe.getObjectVolatile(object, offset) : unsafe.getObject(object, offset);
|
||||
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new UnaccessibleFieldException("The value of the '" + field.getName() + "' field from " + object.getClass().getName() + " cannot be retrieved", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityCollection
|
||||
{
|
||||
final Set<Tidy> candidates;
|
||||
|
|
|
|||
Loading…
Reference in New Issue