У нас есть кусок кода что-то вроде этого
// semi-pseudo code
def result = someList.find { condition == true }
(someList
может быть нулевым, но это нормально в groovy, так как null.find{…}
работает нормально.)
Эта строка кода выполняется в действии контроллера Grails и развернута в производственном процессе на сервере. Через определенный промежуток времени (иногда часы, иногда дольше) вышеприведенная строка кода начнет выдавать исключение NullPointerException - и как только он начинает выбрасывать NPE, он всегда выбрасывает NPE.
Посредством отладки мы доказали, что он работает нормально, даже когда someList имеет значение null (вплоть до получения, казалось бы, случайного первого NPE)… также благодаря отладке мы смогли получить более подробную трассировку стека, которая указала, что ошибка была в MetaClassRegistryImpl Groovy. .java строка 214.
Я погуглил каждую комбинацию, которую могу придумать, чтобы увидеть, есть ли какие-либо известные ошибки Groovy, но не нашел ничего ценного.
(используется Grails 1.3.7, то есть Groovy 1.7.8)
Сценарий JMeter был настроен для запуска серии взаимодействий сайта, что делает эту проблему практически повторяемой. Сценарий будет проходить по сериям 50-100, а затем начинает появляться ошибка - как только ошибка появляется, она всегда находится в ошибке, пока приложение не будет повторно развернуто на сервере (Glassfish).
Трассировка через отличный код выглядит примерно так:
//AbstractCallSite.java
public Object call(Object receiver, Object arg1) throws Throwable {
return call(receiver, ArrayUtil.createArray(arg1));
}
//PerInstancePojoMetaClassSite.java
public Object call(Object receiver, Object[] args) throws Throwable {
if (info.hasPerInstanceMetaClasses()) {
try {
return InvokerHelper.getMetaClass(receiver).invokeMethod(receiver, name, args);
} catch (GroovyRuntimeException gre) {
throw ScriptBytecodeAdapter.unwrap(gre);
}
} else {
return CallSiteArray.defaultCall(this, receiver, args);
}
}
//InvokerHelper.java
public static MetaClass getMetaClass(Object object) {
if (object instanceof GroovyObject)
return ((GroovyObject) object).getMetaClass();
else
return ((MetaClassRegistryImpl) GroovySystem.getMetaClassRegistry()).getMetaClass(object);
}
//MetaClassRegistryImpl.java
public MetaClass getMetaClass(Object obj) {
return ClassInfo.getClassInfo(obj.getClass()).getMetaClass(obj);
}
Так что кажется, что NPE находится на obj.getClass()
- если это так, я немного сбит с толку, как это работает, когда someList
равно нулю (но это отдельная тема).
FWIW, мы не делаем какие-либо собственные мета-классы на уровне класса или экземпляра на someList
.
Есть ли ошибка в Groovy или что мы можем делать неправильно, чтобы вызвать (случайный) NPE глубоко в коде Groovy?
update-
Наблюдение состоит в том, что someList
устанавливается в значение 'java null' вместо 'groovy null' (NullObject
). Объект приходит с карты (контекст потока) через поток в действии контроллера ...
class SomeController {
def someActionFlow = {
action {
def someList = flow.someList
}
}
}
Речь идет о том, что когда flow.someList никогда не задавался, он всегда должен быть нулевым (groovy null). flow
- это просто карта, так же как и flow.get('someList')
Приведенный выше код отлично работает для неизвестного числа итераций, а затем начинает возвращать 'java nulls' вместо 'groovy nulls'.