Fixng deadlock with VClassGroupCache building. NIHVIVO-2763

This commit is contained in:
briancaruso 2011-06-23 15:44:45 +00:00
parent 676b79a0c3
commit 68b9c1d9b5
2 changed files with 235 additions and 215 deletions

View file

@ -35,10 +35,7 @@ public class BrowseController extends FreemarkerHttpServlet {
Map<String, Object> body = new HashMap<String, Object>(); Map<String, Object> body = new HashMap<String, Object>();
String message = null; String message = null;
String templateName = TEMPLATE_DEFAULT; String templateName = TEMPLATE_DEFAULT;
if ( vreq.getParameter("clearcache") != null ) //mainly for debugging
clearGroupCache();
List<VClassGroup> groups = null; List<VClassGroup> groups = null;
VClassGroupCache vcgc = VClassGroupCache.getVClassGroupCache(getServletContext()); VClassGroupCache vcgc = VClassGroupCache.getVClassGroupCache(getServletContext());
if ( vcgc == null ) { if ( vcgc == null ) {
@ -58,10 +55,13 @@ public class BrowseController extends FreemarkerHttpServlet {
templateName = Template.TITLED_MESSAGE.toString(); templateName = Template.TITLED_MESSAGE.toString();
} }
if ( vreq.getParameter("clearcache") != null ) //mainly for debugging
clearGroupCache();
return new TemplateResponseValues(templateName, body); return new TemplateResponseValues(templateName, body);
} }
protected void clearGroupCache(){ protected void clearGroupCache(){
VClassGroupCache.getVClassGroupCache(getServletContext()).clearGroupCache(); VClassGroupCache.getVClassGroupCache(getServletContext()).requestCacheUpdate();
} }
} }

View file

@ -3,9 +3,10 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -30,172 +31,149 @@ import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilterUtils;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters;
import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup; import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup;
public class VClassGroupCache{ public class VClassGroupCache {
private static final Log log = LogFactory.getLog(VClassGroupCache.class); private static final Log log = LogFactory.getLog(VClassGroupCache.class);
private static final String ATTRIBUTE_NAME = "VClassGroupCache"; private static final String ATTRIBUTE_NAME = "VClassGroupCache";
private static final boolean ORDER_BY_DISPLAYRANK = true; private static final boolean ORDER_BY_DISPLAYRANK = true;
private static final boolean INCLUDE_UNINSTANTIATED = true; private static final boolean INCLUDE_UNINSTANTIATED = true;
private static final boolean INCLUDE_INDIVIDUAL_COUNT = true; private static final boolean INCLUDE_INDIVIDUAL_COUNT = true;
/** /**
* Use getVClassGroupCache(ServletContext) to get a VClassGroupCache. * This is the cache of VClassGroups. It is a list of VClassGroups. If this
* is null then the cache is not built.
*/ */
public static VClassGroupCache getVClassGroupCache(ServletContext sc){
return (VClassGroupCache) sc.getAttribute(ATTRIBUTE_NAME);
}
/** Indicates that a rebuild of the VCLassGroupCache is needed. */
private Boolean _rebuildRequested;
/** This is the cache of VClassGroups. It is a list of VClassGroups.
* If this is null then the cache is not built. */
private List<VClassGroup> _groupList; private List<VClassGroup> _groupList;
private final RebuildGroupCacheThread _cacheRebuildThread; /**
* Also keep track of the classes here, makes it easier to get the counts
* and other information */
private Map<String, VClass> VclassMap = new HashMap<String, VClass>();
private final RebuildGroupCacheThread _cacheRebuildThread;
/**
* Need a pointer to the context to get DAOs and models.
*/
private final ServletContext context; private final ServletContext context;
//Also keep track of the classes here, makes it easier to get the counts and other information
private HashMap<String, VClass> VclassMap = new HashMap<String, VClass>();
private VClassGroupCache(ServletContext context) { private VClassGroupCache(ServletContext context) {
this.context = context; this.context = context;
this._groupList = null; this._groupList = null;
if( AbortStartup.isStartupAborted(context)){ if (AbortStartup.isStartupAborted(context)) {
_cacheRebuildThread = null; _cacheRebuildThread = null;
return; return;
} }
/* Need to register for changes of rdf:type for individuals in abox
* and for changes of classgroups for classes. */
VClassGroupCacheChangeListener bccl = new VClassGroupCacheChangeListener(); VClassGroupCacheChangeListener bccl = new VClassGroupCacheChangeListener();
ModelContext.registerListenerForChanges(context, bccl); ModelContext.registerListenerForChanges(context, bccl);
//
// ModelContext.getJenaOntModel(context).register(bccl);
// ModelContext.getBaseOntModel(context).register(bccl);
// ModelContext.getInferenceOntModel(context).register(bccl);
// ModelContext.getUnionOntModelSelector(context).getABoxModel().register(bccl);
// ModelContext.getBaseOntModelSelector(context).getABoxModel().register(bccl);
// ModelContext.getInferenceOntModelSelector(context).getABoxModel().register(bccl);
_rebuildRequested = true;
_cacheRebuildThread = new RebuildGroupCacheThread(this); _cacheRebuildThread = new RebuildGroupCacheThread(this);
_cacheRebuildThread.setDaemon(true); _cacheRebuildThread.setDaemon(true);
_cacheRebuildThread.start(); _cacheRebuildThread.start();
_cacheRebuildThread.informOfQueueChange(); _cacheRebuildThread.informOfQueueChange();
} }
public void clearGroupCache(){ public synchronized VClassGroup getGroup(String vClassGroupURI) {
_groupList.clear(); if (vClassGroupURI == null || vClassGroupURI.isEmpty())
}
public VClassGroup getGroup( String vClassGroupURI ){
if( vClassGroupURI == null || vClassGroupURI.isEmpty() )
return null; return null;
List<VClassGroup> cgList = getGroups(); List<VClassGroup> cgList = getGroups();
for( VClassGroup cg : cgList ){ for (VClassGroup cg : cgList) {
if( vClassGroupURI.equals( cg.getURI())) if (vClassGroupURI.equals(cg.getURI()))
return cg; return cg;
} }
return null; return null;
} }
/**
* @deprecated use getGroups() instead.
*/
public List<VClassGroup> getGroups( int portalId ){
//bdc34: this has been de-portaled.
if( _groupList == null )
return rebuildCache();
else
return _groupList;
}
public List<VClassGroup> getGroups( ){
if( _groupList == null )
return rebuildCache();
else
return _groupList;
}
//Get specific VClass corresponding to Map
public VClass getCachedVClass(String classUri) {
if(VclassMap.containsKey(classUri)) {
return VclassMap.get(classUri);
}
return null;
}
private synchronized void requestCacheUpdate(){
log.debug("requesting update");
_rebuildRequested = true;
_cacheRebuildThread.informOfQueueChange();
}
protected synchronized List<VClassGroup> rebuildCache(){ public synchronized List<VClassGroup> getGroups() {
long start = System.currentTimeMillis(); if (_groupList == null){
try{ log.error("VClassGroup cache has not been created");
WebappDaoFactory wdFactory = (WebappDaoFactory)context.getAttribute("webappDaoFactory");
if( wdFactory == null ){
log.error("Unable to rebuild cache: could not get 'webappDaoFactory' from Servletcontext");
return Collections.emptyList();
}
VitroFilters vFilters = VitroFilterUtils.getPublicFilter(context);
WebappDaoFactory filteringDaoFactory = new WebappDaoFactoryFiltering(wdFactory,vFilters);
// BJL23: You may be wondering, why this extra method?
// Can't we just use the filtering DAO?
// Yes, but using the filtered DAO involves an expensive method
// called correctVClassCounts() that requires each individual
// in a VClass to be retrieved and filtered. This is fine in memory,
// but awful when using a database. We can't (yet) avoid all
// this work when portal filtering is involved, but we can
// short-circuit it when we have a single portal by using
// the filtering DAO only to filter groups and classes,
// and the unfiltered DAO to get the counts.
List<VClassGroup> unfilteredGroups = getGroups(wdFactory.getVClassGroupDao(), INCLUDE_INDIVIDUAL_COUNT);
List<VClassGroup> filteredGroups = getGroups(filteringDaoFactory.getVClassGroupDao(), !INCLUDE_INDIVIDUAL_COUNT);
List<VClassGroup> groups = removeFilteredOutGroupsAndClasses(unfilteredGroups, filteredGroups);
// Remove classes that have been configured to be hidden from search results.
filteringDaoFactory.getVClassGroupDao().removeClassesHiddenFromSearch(groups);
_groupList = groups;
_rebuildRequested = false;
//Also save the classes and their corresponding VClass objects for use later
for(VClassGroup vcg: groups) {
List<VClass> vclasses = vcg.getVitroClassList();
for(VClass vclass: vclasses) {
String classUri = vclass.getURI();
if(!VclassMap.containsKey(classUri)) {
VclassMap.put(classUri, vclass);
}
}
}
log.info("rebuilt ClassGroup cache in " + (System.currentTimeMillis() - start) + " msec");
return _groupList;
}catch (Exception ex){
log.error("could not rebuild cache", ex);
return Collections.emptyList(); return Collections.emptyList();
}else{
return _groupList;
} }
} }
// Get specific VClass corresponding to Map
public synchronized VClass getCachedVClass(String classUri) {
if( VclassMap != null){
if (VclassMap.containsKey(classUri)) {
return VclassMap.get(classUri);
}
return null;
}else{
log.error("VClassGroup cache has not been created");
return null;
}
}
protected synchronized void setCache(List<VClassGroup> newGroups, Map<String,VClass> classMap){
_groupList = newGroups;
VclassMap = classMap;
}
private List<VClassGroup> getGroups( VClassGroupDao vcgDao , boolean includeIndividualCount ){ public void requestCacheUpdate() {
// Get all classgroups, each populated with a list of their member vclasses log.debug("requesting update");
List<VClassGroup> groups = _cacheRebuildThread.informOfQueueChange();
vcgDao.getPublicGroupsWithVClasses(ORDER_BY_DISPLAYRANK, INCLUDE_UNINSTANTIATED, includeIndividualCount); }
protected void requestStop() {
if (_cacheRebuildThread != null) {
_cacheRebuildThread.kill();
try {
_cacheRebuildThread.join();
} catch (InterruptedException e) {
log.warn("Waiting for the thread to die, but interrupted.", e);
}
}
}
protected VClassGroupDao getVCGDao() {
WebappDaoFactory wdf = (WebappDaoFactory) context.getAttribute("webappDaoFactory");
if (wdf == null) {
log.error("Cannot get webappDaoFactory from context");
return null;
} else
return wdf.getVClassGroupDao();
}
/* **************** static utility methods ***************** */
protected static List<VClassGroup> getGroups(VClassGroupDao vcgDao,
boolean includeIndividualCount) {
// Get all classgroups, each populated with a list of their member vclasses
List<VClassGroup> groups = vcgDao.getPublicGroupsWithVClasses(
ORDER_BY_DISPLAYRANK, INCLUDE_UNINSTANTIATED,
includeIndividualCount);
// remove classes that have been configured to be hidden from search results // remove classes that have been configured to be hidden from search results
vcgDao.removeClassesHiddenFromSearch(groups); vcgDao.removeClassesHiddenFromSearch(groups);
return groups; return groups;
} }
protected static Map<String,VClass> classMapForGroups( List<VClassGroup> groups){
Map<String,VClass> newClassMap = new HashMap<String,VClass>();
for (VClassGroup vcg : groups) {
List<VClass> vclasses = vcg.getVitroClassList();
for (VClass vclass : vclasses) {
String classUri = vclass.getURI();
if (!newClassMap.containsKey(classUri)) {
newClassMap.put(classUri, vclass);
}
}
}
return newClassMap;
}
private List<VClassGroup> removeFilteredOutGroupsAndClasses(List<VClassGroup> unfilteredGroups, List<VClassGroup> filteredGroups) { protected static List<VClassGroup> removeFilteredOutGroupsAndClasses(
List<VClassGroup> unfilteredGroups, List<VClassGroup> filteredGroups) {
List<VClassGroup> groups = new ArrayList<VClassGroup>(); List<VClassGroup> groups = new ArrayList<VClassGroup>();
Set<String> allowedGroups = new HashSet<String>(); Set<String> allowedGroups = new HashSet<String>();
Set<String> allowedVClasses = new HashSet<String>(); Set<String> allowedVClasses = new HashSet<String>();
@ -223,116 +201,158 @@ public class VClassGroupCache{
} }
return groups; return groups;
} }
private void requestStop() {
if( _cacheRebuildThread != null ){
_cacheRebuildThread.kill();
try {
_cacheRebuildThread.join();
} catch (InterruptedException e) {
log.warn("Waiting for the thread to die, but interrupted.", e);
}
}
}
protected VClassGroupDao getVCGDao(){
WebappDaoFactory wdf =(WebappDaoFactory)context.getAttribute("webappDaoFactory");
if( wdf == null ){
log.error("Cannot get webappDaoFactory from context");
return null;
}else
return wdf.getVClassGroupDao();
}
/* ****************** Jena Model Change Listener***************************** */
private class VClassGroupCacheChangeListener extends StatementListener {
public void addedStatement(Statement stmt) {
checkAndDoUpdate(stmt);
}
public void removedStatement(Statement stmt) {
checkAndDoUpdate(stmt);
}
private void checkAndDoUpdate(Statement stmt){
if( stmt==null ) return;
if( log.isDebugEnabled()){
log.debug("subject: " + stmt.getSubject().getURI());
log.debug("predicate: " + stmt.getPredicate().getURI());
}
if( RDF.type.getURI().equals( stmt.getPredicate().getURI()) ){
requestCacheUpdate();
} else if( VitroVocabulary.IN_CLASSGROUP.equals( stmt.getPredicate().getURI() )){
requestCacheUpdate();
}
}
}
/* ******************** RebuildGroupCacheThread **************** */ /* ******************** RebuildGroupCacheThread **************** */
protected class RebuildGroupCacheThread extends Thread { protected class RebuildGroupCacheThread extends Thread {
private final VClassGroupCache cache; private final VClassGroupCache cache;
private final AtomicLong queueChangeMillis = new AtomicLong(); private long queueChangeMillis = 0L;
private long timeToBuildLastCache = 100L; //in msec
private boolean rebuildRequested = false;
private volatile boolean die = false; private volatile boolean die = false;
RebuildGroupCacheThread(VClassGroupCache cache) { RebuildGroupCacheThread(VClassGroupCache cache) {
super("VClassGroupCache.RebuildGroupCacheThread"); super("VClassGroupCache.RebuildGroupCacheThread");
this.cache = cache; this.cache = cache;
} }
public void run() { public void run() {
while (!die) { while (!die) {
int delay; int delay;
if (_rebuildRequested == Boolean.FALSE) { if ( !rebuildRequested ) {
log.debug("rebuildGroupCacheThread.run() -- queue empty, sleep"); log.debug("rebuildGroupCacheThread.run() -- queue empty, sleep");
delay = 1000 * 60; delay = 1000 * 60;
} else if ((System.currentTimeMillis() - queueChangeMillis.get()) < 200) { } else if ((System.currentTimeMillis() - queueChangeMillis ) < 500) {
log.debug("rebuildGroupCacheThread.run() -- delay start of rebuild"); log.debug("rebuildGroupCacheThread.run() -- delay start of rebuild");
delay = 200; delay = 500;
} else { } else {
log.debug("rebuildGroupCacheThread.run() -- refreshGroupCache()"); log.debug("rebuildGroupCacheThread.run() -- starting rebuildCache()");
cache.rebuildCache(); long start = System.currentTimeMillis();
delay = 0;
} rebuildRequested = false;
rebuildCache( cache );
if (delay > 0) {
synchronized (this) { timeToBuildLastCache = System.currentTimeMillis() - start;
try { log.debug("rebuildGroupCacheThread.run() -- rebuilt cache in "
wait(delay); + timeToBuildLastCache + " msec");
} catch (InterruptedException e) { delay = 0;
log.warn("Waiting " + delay }
+ " milliseconds, but interrupted.", e);
} if (delay > 0) {
} synchronized (this) {
} try {
wait(delay);
} catch (InterruptedException e) {
log.warn("Waiting " + delay
+ " milliseconds, but interrupted.", e);
}
}
}
} }
log.debug("rebuildGroupCacheThread.run() -- die()"); log.debug("rebuildGroupCacheThread.run() -- die()");
} }
synchronized void informOfQueueChange(){ protected void rebuildCache(VClassGroupCache cache) {
queueChangeMillis.set(System.currentTimeMillis()); try {
WebappDaoFactory wdFactory = (WebappDaoFactory) cache.context.getAttribute("webappDaoFactory");
if (wdFactory == null)
log.error("Unable to rebuild cache: could not get 'webappDaoFactory' from Servletcontext");
VitroFilters vFilters = VitroFilterUtils.getPublicFilter(context);
WebappDaoFactory filteringDaoFactory = new WebappDaoFactoryFiltering(wdFactory, vFilters);
// BJL23: You may be wondering, why this extra method?
// Can't we just use the filtering DAO?
// Yes, but using the filtered DAO involves an expensive method
// called correctVClassCounts() that requires each individual
// in a VClass to be retrieved and filtered. This is fine in memory,
// but awful when using a database. We can't (yet) avoid all
// this work when portal filtering is involved, but we can
// short-circuit it when we have a single portal by using
// the filtering DAO only to filter groups and classes,
// and the unfiltered DAO to get the counts.
List<VClassGroup> unfilteredGroups = getGroups(
wdFactory.getVClassGroupDao(), INCLUDE_INDIVIDUAL_COUNT);
List<VClassGroup> filteredGroups = getGroups(
filteringDaoFactory.getVClassGroupDao(),
!INCLUDE_INDIVIDUAL_COUNT);
List<VClassGroup> groups = removeFilteredOutGroupsAndClasses(
unfilteredGroups, filteredGroups);
// Remove classes that have been configured to be hidden from search results.
filteringDaoFactory.getVClassGroupDao()
.removeClassesHiddenFromSearch(groups);
cache.setCache(groups, classMapForGroups(groups));
} catch (Exception ex) {
log.error("could not rebuild cache", ex);
}
}
synchronized void informOfQueueChange() {
queueChangeMillis = System.currentTimeMillis();
rebuildRequested = true;
this.notifyAll(); this.notifyAll();
} }
synchronized void kill(){ synchronized void kill() {
die = true; die = true;
this.notifyAll(); this.notifyAll();
} }
} }
/* ******************** ServletContextListener **************** */ /* ****************** Jena Model Change Listener***************************** */
private class VClassGroupCacheChangeListener extends StatementListener {
public void addedStatement(Statement stmt) {
checkAndDoUpdate(stmt);
}
public void removedStatement(Statement stmt) {
checkAndDoUpdate(stmt);
}
private void checkAndDoUpdate(Statement stmt) {
if (stmt == null)
return;
if (log.isDebugEnabled()) {
log.debug("subject: " + stmt.getSubject().getURI());
log.debug("predicate: " + stmt.getPredicate().getURI());
}
if (RDF.type.getURI().equals(stmt.getPredicate().getURI())) {
requestCacheUpdate();
} else if (VitroVocabulary.IN_CLASSGROUP.equals(stmt.getPredicate()
.getURI())) {
requestCacheUpdate();
}
}
}
/* ******************** ServletContextListener **************** */
public static class Setup implements ServletContextListener { public static class Setup implements ServletContextListener {
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext(); ServletContext servletContext = sce.getServletContext();
servletContext.setAttribute(ATTRIBUTE_NAME, new VClassGroupCache(servletContext) ); servletContext.setAttribute(ATTRIBUTE_NAME, new VClassGroupCache(
servletContext));
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
Object o = sce.getServletContext().getAttribute(ATTRIBUTE_NAME); Object o = sce.getServletContext().getAttribute(ATTRIBUTE_NAME);
if (o instanceof VClassGroupCache ) { if (o instanceof VClassGroupCache) {
((VClassGroupCache) o).requestStop(); ((VClassGroupCache) o).requestStop();
} }
} }
} }
/**
* Use getVClassGroupCache(ServletContext) to get a VClassGroupCache.
*/
public static VClassGroupCache getVClassGroupCache(ServletContext sc) {
return (VClassGroupCache) sc.getAttribute(ATTRIBUTE_NAME);
}
} }