Grails Groovy在list.find调用中提供NPE,但仅在一段时间后提供

Grails Groovy在list.find调用中提供NPE,但仅在一段时间后提供,grails,groovy,nullpointerexception,Grails,Groovy,Nullpointerexception,我们有一段类似这样的代码 // semi-pseudo code def result = someList.find { condition == true } (someList可能为null,但在groovy中这是可以的,因为null.find{…}可以正常工作。) 这一行代码在grails控制器的操作中运行,并在生产中部署到服务器。在一段时间后(有时几个小时,有时更长),上面的代码行将开始抛出NullPointerException——一旦它开始抛出NPE,它总是抛出NPE 通过调试,

我们有一段类似这样的代码

// semi-pseudo code
def result = someList.find { condition == true }
someList
可能为null,但在groovy中这是可以的,因为
null.find{…}
可以正常工作。)

这一行代码在grails控制器的操作中运行,并在生产中部署到服务器。在一段时间后(有时几个小时,有时更长),上面的代码行将开始抛出NullPointerException——一旦它开始抛出NPE,它总是抛出NPE

通过调试,我们已经证明,即使someList为null,它也可以正常工作(直到我们得到看似随机的第一个NPE)……此外,通过调试,我们能够获得更详细的stacktrace,表明Groovy的MetaClassRegistryImpl.java第214行中存在错误

我用谷歌搜索了我能想到的每一个组合,看看是否有任何已知的Groovy bug,但没有发现任何有价值的东西

(它使用的是Grails1.3.7,因此是Groovy1.7.8)

一个JMeter脚本被设置为运行一系列站点交互,使这个问题半可重复。脚本将迭代50-100系列,然后开始出现错误-一旦出现错误,它将始终处于错误状态,直到应用程序重新部署到服务器(Glassfish)

通过groovy代码跟踪,它看起来像这样:

//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
为null时,我有点困惑它是如何工作的(但这是一个单独的主题)

FWIW,我们没有在
someList
上进行任何类或实例级的元类编码

Groovy中是否存在bug,或者我们可能做了什么错事导致Groovy代码中出现(随机)NPE

更新-

观察到
someList
被设置为“javanull”,而不是“groovynull”(
NullObject
)。对象通过控制器动作中的流来自映射(流上下文)

class SomeController {

    def someActionFlow = {
        action {

            def someList = flow.someList

        }
    }
}
所讨论的情况是,当flow.someList从未被设置时,它应该始终为null(groovy null)
flow
只是一个映射,因此它与执行
flow.get('someList')


上述代码在未知的迭代次数下运行良好,然后开始返回“java nulls”而不是“groovy nulls”。

我猜测这取决于
someList
的创建方式。也就是说,如果它是作为

def someList = null
然后Groovy将
NullObject
分配给变量。但是,如果该值作为真正的Java
null
从其他Java组件返回,那么它将抛出NPE。更进一步说,Groovy/Java/JVM中可能存在一些优化,其中callsite缓存导致它总是返回NPE


同样,这只是一个猜测。

修复:与GROOVY-5248(调用站点缓存缺少空检查)类似,在某些情况下添加接收器空检查以避免NPE


提交641c6a8d4b6b3046f4d8a1a2ac5f08f1f2769f0f

也提交fwiw,
def result=someList?.find{condition==true}
使问题消失。因此,虽然安全导航操作符似乎是解决方案,但由于Groovy的NullObject出现了这个问题,有很多已经在生产中的代码需要重构。相关:我也参与了这个项目,我们发现的正是这个
println someList.getClass()
导致了
NullObject
,当然,在抛出NPE之前,它就是java
null
。我们知道这一点,但我们不知道为什么或如何将此对象从
null对象
转换为
null
。因此,问题是它如何变成“Java null”而不是“Groovy null”。我将此作为答案进行检查,尽管我们不知道groovy/grails从从流上下文映射返回null对象切换到为同一调用返回实际java null的根本问题是什么。