Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy getProperties() call invoking getter for non-existent attribute over 1000 times

Ran into this while doing a refactor. Calls to getProperties() were causing our CPU usage to spike. What we discovered is that if you have a getter without an associated attribute, when you make a call to getProperties() that getter is called over 1000 times. The fix/workaround is obvious and we know it has something to do with metaprogramming but why is this happening (what point in the groovy source)? See groovy script code below:

class tester {

    int count = 0

    public getVar() {
       println count++ + " getVar() called!"
       return var
    }
}

def t = new tester()

t.getProperties()

println "done!"

You should see getVar() called over 1000 times. 1068 to be exact for us.

like image 972
compsage Avatar asked Nov 08 '22 21:11

compsage


1 Answers

The question has probably already been answered in the comments but I dug a little deeper to also answer the "what point in the groovy source" part.

When you call getProperties() on the instance of tester Groovy will do its magic and finally call DefaultGroovyMethods#getProperties(Object) which (in Groovy 2.4.7) looks like this:

public static Map getProperties(Object self) {
    List<PropertyValue> metaProps = getMetaPropertyValues(self); // 1
    Map<String, Object> props = new LinkedHashMap<String, Object>(metaProps.size());

    for (PropertyValue mp : metaProps) {
        try {
            props.put(mp.getName(), mp.getValue()); // 2
        } catch (Exception e) {
            LOG.throwing(self.getClass().getName(), "getProperty(" + mp.getName() + ")", e);
        }
    }
    return props;
}

First, Groovy determines the meta properties of the given object (see 1). This will return three properties:

  • var: getter only (getVar()), no setter, no field
  • class: getter only (inherited from Object), no setter, no field
  • count: getter, setter (both generated by Groovy) and field

You can easily verify this by calling t.getMetaPropertyValues().

Next, Groovy tries to get the current value of each property and puts it in a map (see 2). When it reaches var, it remembers that var has a getter (namely getVar()) and calls it. getVar() however, returns var again. For Groovy, this is the exact same property as determined in the first step. Once again, it calls its getter getVar() and the endless loop begins.

At some point, depending on the JVM, this results in a StackOverflowError, which is exactly what this site is all about :-D

like image 50
hzpz Avatar answered Nov 15 '22 05:11

hzpz