Skip to content

Conversation

@flsobral
Copy link
Member

Fixes leaks observed while using JSONFactory and adds some improvements for reflection and JSONFactory performance.

Marked as DRAFT because I'd like some input in how to improve commit messages and documentation for this PR.

@flsobral flsobral added bug Something isn't working All platforms Issue related to the all platforms Performance labels Jul 19, 2021
@flsobral flsobral self-assigned this Jul 19, 2021
flsobral added 8 commits July 19, 2021 20:11
TODO: more info and maybe rename the tag
The items of the array are locked, so the leak itself wasn't that big.
Always use size_t for addresses!
Changing instancefields.h is a PITA because it triggers a full build.
Moving the related declarations to its own header is better.
It makes sense to cache Class and Method references within JSONFactory,
because it will most likely be used for the same class several times.
Especially when considering that classes are locked objects and their
methods cached after the first getMethods call.
@flsobral flsobral force-pushed the bugfix/leaks-and-performance-loss-observed-using-jsonfactory branch from 0f3876d to 3dd1c54 Compare July 19, 2021 23:11
public class JSONFactory {
public static <T> List<T> asList(String json, Class<T> classOfT) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException {
private static Map<Class<?>, Map<String, Method>> classes = new HashMap<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this as a source of cache, one should be aware that it isn't thread safe OR the thread safety must be provided by the implementor

Comment on lines +150 to +154
Map<String, Method> methodCache = classes.get(classOfT);
if (methodCache == null) {
methodCache = mapClassMethodsToJsonNames(classOfT);
classes.put(classOfT, methodCache);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you got a race condition

Maybe trying a double-checked lock?

// lowercased name
methodCache.put(originalName.toLowerCase(), method);
// not found as-is or lowercased? try replacing camel case with underscore
/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it is safe to assume that a private method will be inlined by the javac compiler with proper given optimization, so for code clarity I think that would be better it this snippet that gives snakecase names was removed to a proper method

Also, I think that getABCD will yield as a result a_bc_d. I have no idea if it is intended to behave like this, but I find it odd and surprises me.

T object = null;
try {
object = classOfT.newInstance();
object = classOfT.newInstance();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the creation of a single object based on its class could also be cached, in a similar way that it is possible to cache methods.

It should be easier to read if the creation of the object was simply:

T object = createNewInstanceOfT(classOfT, outerObject);

and let createNewInstanceOfT throws all the exceptions. createNewInstanceOfT could be like this (not taking care of race conditions yet):

private static final Supplier<Object> FORBIDEN = () -> {
  throw new RuntimeException("oops");
};

private static final Function<Object, Object> FORBIDEN_FUNCTION = () -> {
  throw new RuntimeException("oops");
};

private static <T> T createNewInstanceOfT(Class<T> classOfT, Object outerObject) {
  Supplier<T> supplierOfT = (Supplier<T>) cacheCtor.get(claffOfT);
  if (supplierOfT != null && supplierOfT != FORBIDEN) {
    return supplierOfT .get();
  } else if (supplierOfT == null) {
    try {
      T obj = classOfT.newInstance();
      cacheCtor,put(classOfT, classOfT::newInstance); // working around exceptions, not literally RACE-CONDITION-AWARE
      return obj;
    } catch (Exception e) {
      cacheCtor.put(classOfT, FORBIDEN); // mark such that won't try to fetch default ctor again RACE-CONDITION-AWARE
    }
  }
  if (outerObject == null ||  classOfT.getName().indexOf(outerObject.getClass().getName()) == -1) {
    // CANNOT INSTANTIATE
    throw new ProperCantInstantiateException();
  }
  HashMap<Class<?>, Function<Object, T>> mapConstructorOfTWithParam = (Function<Object, T>) cacheCtorWithParam(classOfT);
  if (mapConstructorOfTWithParam != null) {
    Function<Object, T> constructorOfTWithParam = mapConstructorOfTWithParam .get(outerObject.getClass());
    if (constructorOfTWithParam  == FORBIDEN_FUNCTION ) {
      // CANNOT INSTANTIATE
      throw new ProperCantInstantiateException();
    } else if (constructorOfTWithParam != null) {
      return constructorOfTWithParam.apply(outerObject);
    }
  } else {
    mapConstructorOfTWithParam = new HashMap<>();
    cacheCtorWithParam.put(classOfT, mapConstructorOfTWithParam); // RACE-CONDITION-AWARE
  }
  Constructor<T> constructorOfT = classOfT.getDeclaredConstructor(outerObject.getClass());
  if (constructorOfT == null) {
    mapConstructorOfTWithParam.put(outerObject.getClass(), FORBIDEN_FUNCTION); // mark such that won't try to fetch ctor with this class again RACE-CONDITION-AWARE
    // CANNOT INSTANTIATE
    throw new ProperCantInstantiateException();
  }
  mapConstructorOfTWithParam.put(outerObject.getClass(), oObject -> constructorOfT.newInstance(oObject)); // working around exceptions, not literally RACE-CONDITION-AWARE
  return constructorOfT.newInstance(outerObject);
}

I took some extra care with situations where it won't be possible to create such objects, but perharps this shouldn't?
The idea was to get a proper headstart to know which ctor to call, but if it is invalid then MAYBE my worries are just overengineering

}
if (thisClass != c)
{
thisClass = c;
Copy link
Collaborator

@jeffque jeffque Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thisClass the last time I saw it was OBJ_CLASS(regO[code->mtd.this_]). I had not seen it being updated. And c is OBJ_CLASS(regO[code->mtd.this_])

I didn't see any changes in regO nor in mtd.this_.

The only change I see is in line -763/+764 that c = OBJ_CLASS(params.retO); (or could goto throwClassNotFoundException, but then it would be out of this here context). Maybe this jump should be just below said line?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

All platforms Issue related to the all platforms bug Something isn't working Performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants