You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2005/12/20 16:05:28 UTC

svn commit: r358016 - in /incubator/jackrabbit/trunk/jackrabbit: applications/test/workspaces/default/ src/main/config/ src/main/java/org/apache/jackrabbit/core/ src/main/java/org/apache/jackrabbit/core/query/lucene/

Author: mreutegg
Date: Tue Dec 20 07:05:17 2005
New Revision: 358016

URL: http://svn.apache.org/viewcvs?rev=358016&view=rev
Log:
JCR-257: Use separate index for jcr:system tree
- shut down unused query handler when it had been idle for some configurable time

Modified:
    incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/workspace.xml
    incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java

Modified: incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/workspace.xml
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/workspace.xml?rev=358016&r1=358015&r2=358016&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/workspace.xml (original)
+++ incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/workspace.xml Tue Dec 20 07:05:17 2005
@@ -20,6 +20,11 @@
   -->
   <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
     <param name="path" value="${wsp.home}/index" />
+    <!--
+        Set to zero for test purpose. This is *not* recommended at all for a
+        real system!
+    -->
+    <param name="idleTime" value="0"/>
   </SearchIndex>
 </Workspace>
 

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml?rev=358016&r1=358015&r2=358016&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml Tue Dec 20 07:05:17 2005
@@ -214,6 +214,9 @@
             - analyzer: class name of a lucene analyzer to use for fulltext indexing of text.
             - queryClass: class name that implements the javax.jcr.query.Query interface.
               this class must extend the class: org.apache.jackrabbit.core.query.AbstractQueryImpl
+            - idleTime: idle time in seconds after which an unused query handler is shut down.
+              If the query handler is later used again it is automatically started.
+              Default value -1 disables this feature.
 
             Note: all parameters (except path) in this SearchIndex config are default
             values and can be omitted.
@@ -231,6 +234,7 @@
             <param name="autoRepair" value="true"/>
             <param name="analyzer" value="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
             <param name="queryClass" value="org.apache.jackrabbit.core.query.QueryImpl"/>
+            <param name="idleTime" value="-1"/>
         </SearchIndex>
     </Workspace>
 

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java?rev=358016&r1=358015&r2=358016&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java Tue Dec 20 07:05:17 2005
@@ -50,6 +50,12 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.Properties;
+import java.util.WeakHashMap;
+import java.util.Map;
+import java.util.Collections;
 
 /**
  * Acts as a global entry point to execute queries and index nodes.
@@ -80,11 +86,43 @@
     private static final String PARAM_QUERY_IMPL = "queryClass";
 
     /**
+     * Name of the parameter that specifies the idle time for a query handler.
+     */
+    private static final String PARAM_IDLE_TIME = "idleTime";
+
+    /**
      * Name of the default query implementation class.
      */
     private static final String DEFAULT_QUERY_IMPL_CLASS = QueryImpl.class.getName();
 
     /**
+     * Class instance that is shared for all <code>SearchManager</code> instances.
+     * Each workspace will schedule a task to check if the query handler can
+     * be shutdown after it had been idle for some time.
+     */
+    private static final Timer IDLE_TIMER = new Timer(true);
+
+    /**
+     * Idle time in seconds after which the query handler is shut down.
+     */
+    private static final int DEFAULT_IDLE_TIME = -1;
+
+    /**
+     * The time when the query handler was last accessed.
+     */
+    private long lastAccess = System.currentTimeMillis();
+
+    /**
+     * The search configuration.
+     */
+    private final SearchConfig config;
+
+    /**
+     * The node type registry.
+     */
+    private final NodeTypeRegistry ntReg;
+
+    /**
      * The shared item state manager instance for the workspace.
      */
     private final ItemStateManager itemMgr;
@@ -95,9 +133,20 @@
     private final FileSystem fs;
 
     /**
+     * The root node for this search manager.
+     */
+    private final String rootNodeUUID;
+
+    /**
      * QueryHandler where query execution is delegated to
      */
-    private final QueryHandler handler;
+    private QueryHandler handler;
+
+    /**
+     * QueryHandler of the parent search manager or <code>null</code> if there
+     * is none.
+     */
+    private final QueryHandler parentHandler;
 
     /**
      * Namespace resolver that is based on the namespace registry itself.
@@ -105,6 +154,12 @@
     private final NamespaceResolver nsResolver;
 
     /**
+     * UUID of the node that should be excluded from indexing or <code>null</code>
+     * if no node should be excluded.
+     */
+    private final String excludedNodeUUID;
+
+    /**
      * Path that will be excluded from indexing.
      */
     private Path excludePath;
@@ -116,12 +171,35 @@
     private final String queryImplClassName;
 
     /**
+     * Task that checks if the query handler can be shut down because it
+     * had been idle for {@link #idleTime} seconds.
+     */
+    private final TimerTask idleChecker;
+
+    /**
+     * Idle time in seconds. After the query handler had been idle for this
+     * amount of time it is shut down. Defaults to -1 and causes the search
+     * manager to never shut down.
+     */
+    private int idleTime;
+
+    /**
+     * Weakly references all {@link javax.jcr.query.Query} instances created
+     * by this <code>SearchManager</code>.
+     * If this map is empty and this search manager had been idle for at least
+     * {@link #idleTime} seconds, then the query handler is shut down.
+     */
+    private final Map activeQueries = Collections.synchronizedMap(new WeakHashMap() {
+
+    });
+
+    /**
      * Creates a new <code>SearchManager</code>.
      *
-     * @param config           the search configuration.
+     * @param config the search configuration.
      * @param nsReg            the namespace registry.
-     * @param ntReg            the node type registry.
-     * @param itemMgr          the shared item state manager.
+     * @param ntReg the node type registry.
+     * @param itemMgr the shared item state manager.
      * @param rootNodeUUID     the uuid of the root node.
      * @param parentMgr        the parent search manager or <code>null</code> if
      *                         there is no parent search manager.
@@ -138,7 +216,12 @@
                          SearchManager parentMgr,
                          String excludedNodeUUID) throws RepositoryException {
         this.fs = config.getFileSystem();
+        this.config = config;
+        this.ntReg = ntReg;
         this.itemMgr = itemMgr;
+        this.rootNodeUUID = rootNodeUUID;
+        this.parentHandler = (parentMgr != null) ? parentMgr.handler : null;
+        this.excludedNodeUUID = excludedNodeUUID;
         this.nsResolver = new AbstractNamespaceResolver() {
             public String getURI(String prefix) throws NamespaceException {
                 try {
@@ -171,11 +254,13 @@
             nsReg.registerNamespace(NS_FN_PREFIX, NS_FN_URI);
         }
 
-        queryImplClassName = config.getParameters().getProperty(PARAM_QUERY_IMPL, DEFAULT_QUERY_IMPL_CLASS);
-
-        QueryHandler parentHandler = null;
-        if (parentMgr != null) {
-            parentHandler = parentMgr.handler;
+        Properties params = config.getParameters();
+        queryImplClassName = params.getProperty(PARAM_QUERY_IMPL, DEFAULT_QUERY_IMPL_CLASS);
+        String idleTimeString = params.getProperty(PARAM_IDLE_TIME, String.valueOf(DEFAULT_IDLE_TIME));
+        try {
+            idleTime = Integer.decode(idleTimeString).intValue();
+        } catch (NumberFormatException e) {
+            idleTime = DEFAULT_IDLE_TIME;
         }
 
         if (excludedNodeUUID != null) {
@@ -184,14 +269,28 @@
         }
 
         // initialize query handler
-        try {
-            handler = (QueryHandler) config.newInstance();
-            QueryHandlerContext context
-                    = new QueryHandlerContext(fs, itemMgr, rootNodeUUID, ntReg,
-                            parentHandler, excludedNodeUUID);
-            handler.init(context);
-        } catch (Exception e) {
-            throw new RepositoryException(e.getMessage(), e);
+        initializeQueryHandler();
+
+        idleChecker = new TimerTask() {
+            public void run() {
+                if (lastAccess + (idleTime * 1000) < System.currentTimeMillis()) {
+                    int inUse = activeQueries.size();
+                    if (inUse == 0) {
+                        try {
+                            shutdownQueryHandler();
+                        } catch (IOException e) {
+                            log.warn("Unable to shutdown idle query handler", e);
+                        }
+                    } else {
+                        log.debug("SearchManager is idle but " + inUse +
+                                " queries are still in use.");
+                    }
+                }
+            }
+        };
+
+        if (idleTime > -1) {
+            IDLE_TIMER.schedule(idleChecker, 0, 1000);
         }
     }
 
@@ -201,7 +300,9 @@
      */
     public void close() {
         try {
-            handler.close();
+            idleChecker.cancel();
+            shutdownQueryHandler();
+
             if (fs != null) {
                 fs.close();
             }
@@ -230,6 +331,7 @@
                              String statement,
                              String language)
             throws InvalidQueryException, RepositoryException {
+        ensureInitialized();
         AbstractQueryImpl query = createQueryInstance();
         query.init(session, itemMgr, handler, statement, language);
         return query;
@@ -251,6 +353,7 @@
                              ItemManager itemMgr,
                              Node node)
             throws InvalidQueryException, RepositoryException {
+        ensureInitialized();
         AbstractQueryImpl query = createQueryInstance();
         query.init(session, itemMgr, handler, node);
         return query;
@@ -332,12 +435,15 @@
                 return item;
             }
         };
-        try {
-            handler.updateNodes(removedNodes.iterator(), addedStates);
-        } catch (RepositoryException e) {
-            log.error("Error indexing node.", e);
-        } catch (IOException e) {
-            log.error("Error indexing node.", e);
+        if (removedNodes.size() > 0 || addedNodes.size() > 0) {
+            try {
+                ensureInitialized();
+                handler.updateNodes(removedNodes.iterator(), addedStates);
+            } catch (RepositoryException e) {
+                log.error("Error indexing node.", e);
+            } catch (IOException e) {
+                log.error("Error indexing node.", e);
+            }
         }
 
         if (log.isDebugEnabled()) {
@@ -359,6 +465,8 @@
         try {
             Object obj = Class.forName(queryImplClassName).newInstance();
             if (obj instanceof AbstractQueryImpl) {
+                // track query instances
+                activeQueries.put(obj, null);
                 return (AbstractQueryImpl) obj;
             } else {
                 throw new IllegalArgumentException(queryImplClassName +
@@ -366,6 +474,53 @@
             }
         } catch (Throwable t) {
             throw new RepositoryException("Unable to create query: " + t.toString());
+        }
+    }
+
+    //------------------------< internal >--------------------------------------
+
+    /**
+     * Initializes the query handler.
+     *
+     * @throws RepositoryException if the query handler cannot be initialized.
+     */
+    private void initializeQueryHandler() throws RepositoryException {
+        // initialize query handler
+        try {
+            handler = (QueryHandler) config.newInstance();
+            QueryHandlerContext context
+                    = new QueryHandlerContext(fs, itemMgr, rootNodeUUID,
+                            ntReg, parentHandler, excludedNodeUUID);
+            handler.init(context);
+        } catch (Exception e) {
+            throw new RepositoryException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Shuts down the query handler. If the query handler is already shut down
+     * this method does nothing.
+     *
+     * @throws IOException if an error occurs while shutting down the query
+     *                     handler.
+     */
+    private synchronized void shutdownQueryHandler() throws IOException {
+        if (handler != null) {
+            handler.close();
+            handler = null;
+        }
+    }
+
+    /**
+     * Ensures that the query handler is initialized and updates the last
+     * access to the current time.
+     *
+     * @throws RepositoryException if the query handler cannot be initialized.
+     */
+    private synchronized void ensureInitialized() throws RepositoryException {
+        lastAccess = System.currentTimeMillis();
+        if (handler == null) {
+            initializeQueryHandler();
         }
     }
 }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java?rev=358016&r1=358015&r2=358016&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java Tue Dec 20 07:05:17 2005
@@ -164,7 +164,13 @@
     /**
      * Timer to schedule flushes of this index after some idle time.
      */
-    private final Timer flushTimer = new Timer(true);
+    private static final Timer FLUSH_TIMER = new Timer(true);
+
+    /**
+     * Task that is periodically called by {@link #FLUSH_TIMER} and checks
+     * if index should be flushed.
+     */
+    private final TimerTask flushTask;
 
     /**
      * The RedoLog of this <code>MultiIndex</code>.
@@ -189,10 +195,10 @@
     /**
      * Creates a new MultiIndex.
      *
-     * @param indexDir      the base file system
-     * @param handler       the search handler
-     * @param stateMgr      shared item state manager
-     * @param rootUUID      uuid of the root node
+     * @param indexDir the base file system
+     * @param handler the search handler
+     * @param stateMgr shared item state manager
+     * @param rootUUID uuid of the root node
      * @param excludedUUIDs Set&lt;String> that contains uuids that should not
      *                      be indexed nor further traversed.
      * @throws IOException if an error occurs
@@ -206,7 +212,7 @@
         this.indexDir = indexDir;
         this.handler = handler;
         this.cache = new DocNumberCache(handler.getCacheSize());
-        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG));
+        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG)); 
         this.excludedUUIDs = new HashSet(excludedUUIDs);
 
         if (indexNames.exists(indexDir)) {
@@ -277,7 +283,13 @@
             throw new IOException("Error indexing root node: " + e.getMessage());
         }
 
-        startFlushTimer();
+        lastFlushTime = System.currentTimeMillis();
+        flushTask = new TimerTask() {
+            public void run() {
+                checkFlush();
+    }
+        };
+        FLUSH_TIMER.schedule(flushTask, 0, 1000);
     }
 
     /**
@@ -596,7 +608,7 @@
 
         synchronized (this) {
             // stop timer
-            flushTimer.cancel();
+            flushTask.cancel();
 
             // commit / close indexes
             if (multiReader != null) {
@@ -808,7 +820,7 @@
             resetVolatileIndex();
 
             time = System.currentTimeMillis() - time;
-            log.info("Committed in-memory index in " + time + "ms.");
+            log.debug("Committed in-memory index in " + time + "ms.");
         }
     }
 
@@ -902,19 +914,6 @@
     }
 
     /**
-     * Starts the flush timer that periodically checks if the index
-     * should be flushed. The timer task will call {@link #checkFlush()}.
-     */
-    private void startFlushTimer() {
-        lastFlushTime = System.currentTimeMillis();
-        flushTimer.schedule(new TimerTask() {
-            public void run() {
-                checkFlush();
-            }
-        }, 0, 1000);
-    }
-
-    /**
      * Checks the duration between the last commit to this index and the
      * current time and flushes the index (if there are changes at all)
      * if the duration (idle time) is more than {@link SearchIndex#getVolatileIdleTime()}
@@ -927,7 +926,7 @@
                 && idleTime > handler.getVolatileIdleTime() * 1000) {
             try {
                 if (redoLog.hasEntries()) {
-                    log.info("Flushing index after being idle for " +
+                    log.debug("Flushing index after being idle for " +
                             idleTime + " ms.");
                     synchronized (updateMonitor) {
                         updateInProgress = true;

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?rev=358016&r1=358015&r2=358016&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Tue Dec 20 07:05:17 2005
@@ -163,6 +163,12 @@
     private int cacheSize = 1000;
 
     /**
+     * Indicates if this <code>SearchIndex</code> is closed and cannot be used
+     * anymore.
+     */
+    private boolean closed = false;
+
+    /**
      * Default constructor.
      */
     public SearchIndex() {
@@ -211,6 +217,7 @@
                 log.warn("Failed to run consistency check on index: " + e);
             }
         }
+        log.info("Index initialized: " + path);
     }
 
     /**
@@ -247,6 +254,7 @@
      */
     public void updateNodes(Iterator remove, Iterator add)
             throws RepositoryException, IOException {
+        checkOpen();
         index.update(new AbstractIteratorDecorator(remove) {
             public Object next() {
                 String uuid = (String) super.next();
@@ -303,7 +311,8 @@
     public void close() {
         index.close();
         getContext().destroy();
-        log.info("Search index closed.");
+        closed = true;
+        log.info("Index closed: " + path);
     }
 
     /**
@@ -321,6 +330,7 @@
                                   Query query,
                                   QName[] orderProps,
                                   boolean[] orderSpecs) throws IOException {
+        checkOpen();
         QueryHandler parentHandler = getContext().getParentHandler();
         IndexReader parentReader = null;
         if (parentHandler instanceof SearchIndex) {
@@ -730,5 +740,19 @@
             delim = ",";
         }
         return names.toString();
+    }
+
+    //----------------------------< internal >----------------------------------
+
+    /**
+     * Checks if this <code>SearchIndex</code> is open, otherwise throws
+     * an <code>IOException</code>.
+     *
+     * @throws IOException if this <code>SearchIndex</code> had been closed.
+     */
+    private void checkOpen() throws IOException {
+        if (closed) {
+            throw new IOException("query handler closed and cannot be used anymore.");
+}
     }
 }