Problem Statement:
Unclosed resource resolver issue is causing performance impact on the AEM environment
It’s hard to debug and fix the resolver issues
Best practices to close the resolver
Requirement:
Provide all the best practices to close the unclosed resource resolve and in turn improve environment stability.
Introduction:
Usually, we create/open a resource resolver:
- Servlets
- OSGi Services
- Workflows
- Schedulers
Purpose of service user based resolver.
To get access to the paths which is blocked for every one group, make changes and commit changes etc.
Creating Resource resolver instance:
This feature was released with Java 7 and the try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.
So basically, creating the resolver within the calling class like below will auto close:
try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "{service user name}"))){
//business logic
}
And it is highly recommended to use the following APIs as well:
- FileReader
- ZipFile
- BufferedWriter
etc.
Closing the resolver for Queries:
Whenever we come across queries, we need to close the resolver manually even after using the try resource approach:
Try resource Approach:
ResourceResolver resolverResolverLeakingReference = null;
try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "{service user name}"))) {
session = resolver.adaptTo(Session.class);
queryBuilder = resolver.adaptTo(QueryBuilder.class);
PredicateGroup predicateGroup = null;
try {
predicateGroup = PredicateGroup.create(map);
} catch (VerifyError ex) {
predicateGroup = new PredicateGroup();
}
Query query = queryBuilder.createQuery(predicateGroup, session);
SearchResult result = query.getResult();
for (final Hit hit : result.getHits()) {
Resource resource = hit.getResource();
if(resolverResolverLeakingReference == null){
resolverResolverLeakingReference = resource.getResourceResolver();
}
//buisness logic
}
} catch (Exception e) {
log.error("Error fetching locations count results", e);
} finally {
if (resolverResolverLeakingReference != null) {
// Always Close the leaking QueryBuilder resourceResolver.
resolverResolverLeakingReference.close();
}
}
Requet.getResolver Approach:
ResourceResolver leakingResourceResolverReference = null;
try {
Session session = request.getResourceResolver().adaptTo(Session.class);
Map<String, String> map = new HashMap<>();
//map put goes here
Query query = queryBuilder.createQuery(PredicateGroup.create(map), session);
SearchResult result = query.getResult();
for (Hit hit : result.getHits()) {
if(leakingResourceResolverReference == null) {
leakingResourceResolverReference = hit.getResource().getResourceResolver();
}
//buisness logic
}
} catch(Exception e) {
log.info("error::{}",e.getMessage());
} finally {
if(leakingResourceResolverReference != null){
leakingResourceResolverReference.close();
}
}
Or you can also follow this:
SearchResult result;
try {
Session session = request.getResourceResolver().adaptTo(Session.class);
Map<String, String> map = new HashMap<>();
//map put goes here
Query query = queryBuilder.createQuery(PredicateGroup.create(map), session);
result = query.getResult();
for (Hit hit : result.getHits()) {
//buisness logic
}
} catch(Exception e) {
log.info("error::{}",e.getMessage());
} finally {
Iterator<Resource> resources = result.getResources();
if (resources.hasNext()) {
resources.next().getResourceResolver().close();
}
}
Avoid query builder and use SQL2:
try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "{service user name}"))){
String querySt = "SELECT * FROM [nt:base] ";
Iterator<Resource> results = resourceResolver.findResources(querySt, "JCR-SQL2");
//buisness logc
}
Use Java Streams:
You can follow the below blog to use Java stream for executing queries in AEM