XPand performance when using Java extensions
This post is not about the new (cool) Xpand profiler which I haven’t had the time to try out, yet… In one of our projects Xpand performance is critical as generation is part of Checkin/Checkout processes. We were able to bring down generation time from 3 minutes (naive Xtend-based implementation with many little Java extensions calls) to 20 seconds for a big model, invoking the workflow file from Java using the WorkflowRunner class. By then, basically everything was done in a few Java methods and Xpand/Xtend only called the helpers needed.
Letting Java return an empty string rather than doing complicated calculations reduced that time to something like 19.7 seconds. Letting Xtend return the empty string rather than delegating to the Java helper reduced the time to 2 seconds. So there seemed to be a problem when delegating Xtend-calls to Java. Fortunately, I quickly found a forum thread pointing to a bug describing the problem and a possible solution.
What we now did was adding a bean class to the workflow setting the cached resource loader. The following is an adapted implementation I tried out using a slightly modified version of the Xtext sample project. Using that code, generation worked with “Run As MWE Workflow” in both the development and the runtime workspace an as well as with the workflow being invoked upon saving the model file.
import org.eclipse.emf.mwe.core.resources.CachingResourceLoaderImpl;
import org.eclipse.emf.mwe.core.resources.ResourceLoaderFactory;
public class CachingResourceLoaderBean {
public CachingResourceLoaderBean() {
CachingResourceLoaderImpl crl=
new CachingResourceLoaderImpl(ResourceLoaderFactory
.createResourceLoader());
ResourceLoaderFactory.setCurrentThreadResourceLoader(crl);
}
}
This class is called in the workflow before the model reader.
<bean class="package.of.CachingResourceLoaderBean"/>
When now invoking a workflow from java, we do so within a new thread in order to make sure that the resource loader is not changed globally (which is problematic if you want to invoke many different workflows from different projects within the same class).
One strange thing is that I need to start the runtime workspace using the -clean option. Otherwise the following happens (which my be due to an error in the toy-project configuration or the way I invoked the workflow; note that the issue not related to the cached resource loader as this happens as well when this bean class is deactivated): I modify the generator- (or ui-) project’s Manifest, e.g. by reordering the dependency entries. I start the runtime workspace and invoking the workflow from java (save model file) works. However, if I close the runtime workspace and start it again (having made no changes), invoking the workflow by saving the model file now yields the following error.
Exception in thread "Thread-somenumber" java.lang.NoSuchFieldError: callback at org.eclipse.xpand2.Generator.invokeInternal2(Generator.java:298) at org.eclipse.xtend.expression.AbstractExpressionsUsingWorkflowComponent.invokeInternal(AbstractExpressionsUsingWorkflowComponent.java:199) at org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent.invoke(AbstractWorkflowComponent.java:124) at org.eclipse.emf.mwe.core.container.CompositeComponent.internalInvoke(CompositeComponent.java:101) at org.eclipse.emf.mwe.core.container.CompositeComponent.invoke(CompositeComponent.java:86) at org.eclipse.emf.mwe.core.WorkflowRunner.executeWorkflow(WorkflowRunner.java:406) at org.xtext.example.WorkflowThread.run(WorkflowThread.java:28)
This type of error reoccurs after every start of the runtime workspace until I either start it with the -clean option or modify some Manifest. I can live with the -clean option but any hints for solving that problem are appreciated.