You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by is...@apache.org on 2019/05/14 09:13:22 UTC

[lucene-solr] branch jira/solr-13350 created (now b8755aa)

This is an automated email from the ASF dual-hosted git repository.

ishan pushed a change to branch jira/solr-13350
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git.


      at b8755aa  SOLR-13350: Multi-threaded search through an index

This branch includes the following new commits:

     new b8755aa  SOLR-13350: Multi-threaded search through an index

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[lucene-solr] 01/01: SOLR-13350: Multi-threaded search through an index

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/solr-13350
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit b8755aa47727c242d9b5ac7640161eca5d4a4159
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Tue May 14 14:42:46 2019 +0530

    SOLR-13350: Multi-threaded search through an index
---
 .../org/apache/lucene/search/IndexSearcher.java    |   2 +-
 .../org/apache/lucene/search/MultiCollector.java   |   6 +
 .../solr/handler/component/QueryComponent.java     |   2 +-
 .../org/apache/solr/request/SolrRequestInfo.java   |  37 ++
 .../org/apache/solr/search/JoinQParserPlugin.java  | 537 ++++++++++++---------
 .../org/apache/solr/search/SolrIndexSearcher.java  | 217 +++++++--
 .../org/apache/solr/schema/TestOmitPositions.java  |   3 +-
 .../test/org/apache/solr/search/TestFiltering.java |   6 +-
 8 files changed, 531 insertions(+), 279 deletions(-)

diff --git a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
index 689409f..9355a07 100644
--- a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
+++ b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java
@@ -110,7 +110,7 @@ public class IndexSearcher {
   private final LeafSlice[] leafSlices;
 
   // These are only used for multi-threaded search
-  private final ExecutorService executor;
+  protected final ExecutorService executor;
 
   // the default Similarity
   private static final Similarity defaultSimilarity = new BM25Similarity();
diff --git a/lucene/core/src/java/org/apache/lucene/search/MultiCollector.java b/lucene/core/src/java/org/apache/lucene/search/MultiCollector.java
index 82251e4..56965e6 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MultiCollector.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MultiCollector.java
@@ -20,6 +20,7 @@ package org.apache.lucene.search;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.lucene.index.LeafReaderContext;
@@ -115,6 +116,11 @@ public class MultiCollector implements Collector {
     return scoreMode;
   }
 
+  // nocommit: need to raise a LUCENE jira for this?
+  public List<Collector> getCollectors() {
+    return Collections.unmodifiableList(Arrays.asList(collectors));
+  }
+
   @Override
   public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
     final List<LeafCollector> leafCollectors = new ArrayList<>();
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
index fea238b..bf41286 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
@@ -889,7 +889,7 @@ public class QueryComponent extends SearchComponent
         numFound += docs.getNumFound();
 
         NamedList sortFieldValues = (NamedList)(srsp.getSolrResponse().getResponse().get("sort_values"));
-        if (sortFieldValues.size()==0 && // we bypass merging this response only if it's partial itself
+        if (sortFieldValues == null || sortFieldValues.size()==0 && // we bypass merging this response only if it's partial itself
                             thisResponseIsPartial) { // but not the previous one!!
           continue; //fsv timeout yields empty sort_vlaues
         }
diff --git a/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java b/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
index 5995f2b..3de5db4 100644
--- a/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
+++ b/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.solr.common.Callable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.util.ExecutorUtil;
@@ -46,6 +47,8 @@ public class SolrRequestInfo {
   protected TimeZone tz;
   protected ResponseBuilder rb;
   protected List<Closeable> closeHooks;
+  protected List<Callable> initHooks;
+  protected Object initData; // Any additional auxiliary data that needs to be stored
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
@@ -65,6 +68,20 @@ public class SolrRequestInfo {
     threadLocal.set(info);
   }
 
+  public static void init() {
+    SolrRequestInfo info = threadLocal.get();
+    if (info != null && info.initHooks != null) {
+      for (Callable hook : info.initHooks) {
+        try {
+          hook.call(null);
+        } catch (Exception e) {
+          SolrException.log(log, "Exception during init hook", e);
+        }
+      }
+    }
+  }
+
+
   public static void clearRequestInfo() {
     try {
       SolrRequestInfo info = threadLocal.get();
@@ -139,6 +156,15 @@ public class SolrRequestInfo {
     this.rb = rb;
   }
 
+  public void addInitHook(Callable hook) {
+    // is this better here, or on SolrQueryRequest?
+    synchronized (this) {
+      if (initHooks == null) {
+        initHooks = new LinkedList<>();
+      }
+      initHooks.add(hook);
+    }
+  }
   public void addCloseHook(Closeable hook) {
     // is this better here, or on SolrQueryRequest?
     synchronized (this) {
@@ -164,6 +190,7 @@ public class SolrRequestInfo {
           ctx.set(null);
           SolrRequestInfo.setRequestInfo(me);
         }
+        SolrRequestInfo.init();
       }
 
       @Override
@@ -172,4 +199,14 @@ public class SolrRequestInfo {
       }
     };
   }
+
+  public Object getInitData() {
+    return initData;
+  }
+
+  public void setInitData(Object initData) {
+    synchronized (this) {
+      this.initData = initData;
+    }
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
index c6fb0de..138c56f 100644
--- a/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
@@ -18,6 +18,7 @@ package org.apache.solr.search;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -44,6 +45,7 @@ import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.lucene.util.StringHelper;
+import org.apache.solr.common.Callable;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.SimpleOrderedMap;
@@ -59,6 +61,8 @@ import org.apache.solr.search.join.GraphPointsCollector;
 import org.apache.solr.search.join.ScoreJoinQParserPlugin;
 import org.apache.solr.util.RTimer;
 import org.apache.solr.util.RefCounted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class JoinQParserPlugin extends QParserPlugin {
   public static final String NAME = "join";
@@ -124,6 +128,8 @@ public class JoinQParserPlugin extends QParserPlugin {
     };
   }
 
+  public static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
   /**
    * A helper method for other plugins to create (non-scoring) JoinQueries wrapped around arbitrary queries against the same core.
    * 
@@ -176,23 +182,21 @@ class JoinQuery extends Query {
   }
 
   private class JoinQueryWeight extends ConstantScoreWeight {
-    SolrIndexSearcher fromSearcher;
-    RefCounted<SolrIndexSearcher> fromRef;
     SolrIndexSearcher toSearcher;
     ResponseBuilder rb;
     ScoreMode scoreMode;
+    final boolean isSameCoreJoin;
 
     public JoinQueryWeight(SolrIndexSearcher searcher, ScoreMode scoreMode, float boost) {
       super(JoinQuery.this, boost);
       this.scoreMode = scoreMode;
-      this.fromSearcher = searcher;
       SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
       if (info != null) {
         rb = info.getResponseBuilder();
       }
 
       if (fromIndex == null) {
-        this.fromSearcher = searcher;
+        isSameCoreJoin = true;
       } else {
         if (info == null) {
           throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cross-core join must have SolrRequestInfo");
@@ -200,42 +204,42 @@ class JoinQuery extends Query {
 
         CoreContainer container = searcher.getCore().getCoreContainer();
         final SolrCore fromCore = container.getCore(fromIndex);
+        try {
+          if (fromCore == null) {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cross-core join: no such core " + fromIndex);
+          }
 
-        if (fromCore == null) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cross-core join: no such core " + fromIndex);
+          if (info.getReq().getCore() == fromCore) {
+            isSameCoreJoin = true;
+          } else {
+            isSameCoreJoin = false;
+          }
+        } finally {
+          fromCore.close();
         }
+          info.addInitHook(new Callable<Object>() {
 
-        if (info.getReq().getCore() == fromCore) {
-          // if this is the same core, use the searcher passed in... otherwise we could be warming and
-          // get an older searcher from the core.
-          fromSearcher = searcher;
-        } else {
-          // This could block if there is a static warming query with a join in it, and if useColdSearcher is true.
-          // Deadlock could result if two cores both had useColdSearcher and had joins that used eachother.
-          // This would be very predictable though (should happen every time if misconfigured)
-          fromRef = fromCore.getSearcher(false, true, null);
-
-          // be careful not to do anything with this searcher that requires the thread local
-          // SolrRequestInfo in a manner that requires the core in the request to match
-          fromSearcher = fromRef.get();
-        }
+            @Override
+            public void call(Object data) {
+              if (!isSameCoreJoin ) {
+                info.setInitData(container.getCore(fromIndex));
+              }
+            }
+          });
 
-        if (fromRef != null) {
-          final RefCounted<SolrIndexSearcher> ref = fromRef;
+          JoinQParserPlugin.log.info("Adding hook...");
           info.addCloseHook(new Closeable() {
             @Override
             public void close() {
-              ref.decref();
+              synchronized (info) {
+                SolrCore fromCore = (SolrCore) info.getInitData();
+                if (fromCore != null) {
+                  fromCore.close();
+                  info.setInitData(null); // unset
+                }
+              }
             }
           });
-        }
-
-        info.addCloseHook(new Closeable() {
-          @Override
-          public void close() {
-            fromCore.close();
-          }
-        });
 
       }
       this.toSearcher = searcher;
@@ -309,248 +313,301 @@ class JoinQuery extends Query {
 
 
     public DocSet getDocSet() throws IOException {
-      SchemaField fromSchemaField = fromSearcher.getSchema().getField(fromField);
-      SchemaField toSchemaField = toSearcher.getSchema().getField(toField);
-
-      boolean usePoints = false;
-      if (toSchemaField.getType().isPointField()) {
-        if (!fromSchemaField.hasDocValues()) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "join from field " + fromSchemaField + " should have docValues to join with points field " + toSchemaField);
+      RefCounted<SolrIndexSearcher> fromSearcherRef = null;
+      SolrIndexSearcher fromSearcher;
+      SolrCore fromCore = null;
+      boolean openedFromCoreHere = false;
+      if (isSameCoreJoin) {
+        // if this is the same core, use the searcher passed in... otherwise we could be warming and
+        // get an older searcher from the core.
+        fromSearcher = toSearcher;
+      } else {
+        fromCore = (SolrCore) SolrRequestInfo.getRequestInfo().getInitData();
+        if (fromCore == null) {
+          fromCore = toSearcher.getCore().getCoreContainer().getCore(fromIndex);
+          openedFromCoreHere = true;
         }
-        usePoints = true;
+        fromSearcherRef = fromCore.getSearcher();
+        fromSearcher = fromSearcherRef.get();
       }
-
-      if (!usePoints) {
-        return getDocSetEnumerate();
+      try {
+        SchemaField fromSchemaField = fromSearcher.getSchema().getField(fromField);
+        SchemaField toSchemaField = toSearcher.getSchema().getField(toField);
+  
+        boolean usePoints = false;
+        if (toSchemaField.getType().isPointField()) {
+          if (!fromSchemaField.hasDocValues()) {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "join from field " + fromSchemaField + " should have docValues to join with points field " + toSchemaField);
+          }
+          usePoints = true;
+        }
+  
+        if (!usePoints) {
+          return getDocSetEnumerate();
+        }
+  
+        // point fields
+        GraphPointsCollector collector = new GraphPointsCollector(fromSchemaField, null, null);
+        fromSearcher.search(q, collector);
+        Query resultQ = collector.getResultQuery(toSchemaField, false);
+        // don't cache the resulting docSet... the query may be very large.  Better to cache the results of the join query itself
+        DocSet result = resultQ==null ? DocSet.EMPTY : toSearcher.getDocSetNC(resultQ, null);
+        return result;
+      } finally {
+        if (fromSearcherRef != null) {
+          fromSearcherRef.decref();
+        }
+        if (fromCore != null && openedFromCoreHere) {
+          fromCore.close();
+        }
       }
-
-      // point fields
-      GraphPointsCollector collector = new GraphPointsCollector(fromSchemaField, null, null);
-      fromSearcher.search(q, collector);
-      Query resultQ = collector.getResultQuery(toSchemaField, false);
-      // don't cache the resulting docSet... the query may be very large.  Better to cache the results of the join query itself
-      DocSet result = resultQ==null ? DocSet.EMPTY : toSearcher.getDocSetNC(resultQ, null);
-      return result;
     }
 
 
 
     public DocSet getDocSetEnumerate() throws IOException {
-      FixedBitSet resultBits = null;
-
-      // minimum docFreq to use the cache
-      int minDocFreqFrom = Math.max(5, fromSearcher.maxDoc() >> 13);
-      int minDocFreqTo = Math.max(5, toSearcher.maxDoc() >> 13);
-
-      // use a smaller size than normal since we will need to sort and dedup the results
-      int maxSortedIntSize = Math.max(10, toSearcher.maxDoc() >> 10);
-
-      DocSet fromSet = fromSearcher.getDocSet(q);
-      fromSetSize = fromSet.size();
-
-      List<DocSet> resultList = new ArrayList<>(10);
-
-      // make sure we have a set that is fast for random access, if we will use it for that
-      DocSet fastForRandomSet = fromSet;
-      if (minDocFreqFrom>0 && fromSet instanceof SortedIntDocSet) {
-        SortedIntDocSet sset = (SortedIntDocSet)fromSet;
-        fastForRandomSet = new HashDocSet(sset.getDocs(), 0, sset.size());
-      }
-
-
-      LeafReader fromReader = fromSearcher.getSlowAtomicReader();
-      LeafReader toReader = fromSearcher==toSearcher ? fromReader : toSearcher.getSlowAtomicReader();
-      Terms terms = fromReader.terms(fromField);
-      Terms toTerms = toReader.terms(toField);
-      if (terms == null || toTerms==null) return DocSet.EMPTY;
-      String prefixStr = TrieField.getMainValuePrefix(fromSearcher.getSchema().getFieldType(fromField));
-      BytesRef prefix = prefixStr == null ? null : new BytesRef(prefixStr);
-
-      BytesRef term = null;
-      TermsEnum  termsEnum = terms.iterator();
-      TermsEnum  toTermsEnum = toTerms.iterator();
-      SolrIndexSearcher.DocsEnumState fromDeState = null;
-      SolrIndexSearcher.DocsEnumState toDeState = null;
-
-      if (prefix == null) {
-        term = termsEnum.next();
+      RefCounted<SolrIndexSearcher> fromSearcherRef = null;
+      SolrIndexSearcher fromSearcher;
+      SolrCore fromCore = null;
+      boolean openedFromCoreHere = false;
+      if (isSameCoreJoin) {
+        fromSearcher = toSearcher;
       } else {
-        if (termsEnum.seekCeil(prefix) != TermsEnum.SeekStatus.END) {
-          term = termsEnum.term();
+        fromCore = (SolrCore) SolrRequestInfo.getRequestInfo().getInitData();
+        if (fromCore == null) {
+          fromCore = toSearcher.getCore().getCoreContainer().getCore(fromIndex);
+          openedFromCoreHere = true;
         }
+        fromSearcherRef = fromCore.getSearcher();
+        fromSearcher = fromSearcherRef.get();
       }
+      try {
 
-      Bits fromLiveDocs = fromSearcher.getLiveDocsBits();
-      Bits toLiveDocs = fromSearcher == toSearcher ? fromLiveDocs : toSearcher.getLiveDocsBits();
-
-      fromDeState = new SolrIndexSearcher.DocsEnumState();
-      fromDeState.fieldName = fromField;
-      fromDeState.liveDocs = fromLiveDocs;
-      fromDeState.termsEnum = termsEnum;
-      fromDeState.postingsEnum = null;
-      fromDeState.minSetSizeCached = minDocFreqFrom;
-
-      toDeState = new SolrIndexSearcher.DocsEnumState();
-      toDeState.fieldName = toField;
-      toDeState.liveDocs = toLiveDocs;
-      toDeState.termsEnum = toTermsEnum;
-      toDeState.postingsEnum = null;
-      toDeState.minSetSizeCached = minDocFreqTo;
-
-      while (term != null) {
-        if (prefix != null && !StringHelper.startsWith(term, prefix))
-          break;
-
-        fromTermCount++;
-
-        boolean intersects = false;
-        int freq = termsEnum.docFreq();
-        fromTermTotalDf++;
-
-        if (freq < minDocFreqFrom) {
-          fromTermDirectCount++;
-          // OK to skip liveDocs, since we check for intersection with docs matching query
-          fromDeState.postingsEnum = fromDeState.termsEnum.postings(fromDeState.postingsEnum, PostingsEnum.NONE);
-          PostingsEnum postingsEnum = fromDeState.postingsEnum;
-
-          if (postingsEnum instanceof MultiPostingsEnum) {
-            MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
-            int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
-            outer: for (int subindex = 0; subindex<numSubs; subindex++) {
-              MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
-              if (sub.postingsEnum == null) continue;
-              int base = sub.slice.start;
+        FixedBitSet resultBits = null;
+  
+        // minimum docFreq to use the cache
+        int minDocFreqFrom = Math.max(5, fromSearcher.maxDoc() >> 13);
+        int minDocFreqTo = Math.max(5, toSearcher.maxDoc() >> 13);
+  
+        // use a smaller size than normal since we will need to sort and dedup the results
+        int maxSortedIntSize = Math.max(10, toSearcher.maxDoc() >> 10);
+  
+        DocSet fromSet = fromSearcher.getDocSet(q);
+        fromSetSize = fromSet.size();
+  
+        List<DocSet> resultList = new ArrayList<>(10);
+  
+        // make sure we have a set that is fast for random access, if we will use it for that
+        DocSet fastForRandomSet = fromSet;
+        if (minDocFreqFrom>0 && fromSet instanceof SortedIntDocSet) {
+          SortedIntDocSet sset = (SortedIntDocSet)fromSet;
+          fastForRandomSet = new HashDocSet(sset.getDocs(), 0, sset.size());
+        }
+  
+  
+        LeafReader fromReader = fromSearcher.getSlowAtomicReader();
+        LeafReader toReader = fromSearcher==toSearcher ? fromReader : toSearcher.getSlowAtomicReader();
+        Terms terms = fromReader.terms(fromField);
+        Terms toTerms = toReader.terms(toField);
+        if (terms == null || toTerms==null) return DocSet.EMPTY;
+        String prefixStr = TrieField.getMainValuePrefix(fromSearcher.getSchema().getFieldType(fromField));
+        BytesRef prefix = prefixStr == null ? null : new BytesRef(prefixStr);
+  
+        BytesRef term = null;
+        TermsEnum  termsEnum = terms.iterator();
+        TermsEnum  toTermsEnum = toTerms.iterator();
+        SolrIndexSearcher.DocsEnumState fromDeState = null;
+        SolrIndexSearcher.DocsEnumState toDeState = null;
+  
+        if (prefix == null) {
+          term = termsEnum.next();
+        } else {
+          if (termsEnum.seekCeil(prefix) != TermsEnum.SeekStatus.END) {
+            term = termsEnum.term();
+          }
+        }
+  
+        Bits fromLiveDocs = fromSearcher.getLiveDocsBits();
+        Bits toLiveDocs = fromSearcher == toSearcher ? fromLiveDocs : toSearcher.getLiveDocsBits();
+  
+        fromDeState = new SolrIndexSearcher.DocsEnumState();
+        fromDeState.fieldName = fromField;
+        fromDeState.liveDocs = fromLiveDocs;
+        fromDeState.termsEnum = termsEnum;
+        fromDeState.postingsEnum = null;
+        fromDeState.minSetSizeCached = minDocFreqFrom;
+  
+        toDeState = new SolrIndexSearcher.DocsEnumState();
+        toDeState.fieldName = toField;
+        toDeState.liveDocs = toLiveDocs;
+        toDeState.termsEnum = toTermsEnum;
+        toDeState.postingsEnum = null;
+        toDeState.minSetSizeCached = minDocFreqTo;
+  
+        while (term != null) {
+          if (prefix != null && !StringHelper.startsWith(term, prefix))
+            break;
+  
+          fromTermCount++;
+  
+          boolean intersects = false;
+          int freq = termsEnum.docFreq();
+          fromTermTotalDf++;
+  
+          if (freq < minDocFreqFrom) {
+            fromTermDirectCount++;
+            // OK to skip liveDocs, since we check for intersection with docs matching query
+            fromDeState.postingsEnum = fromDeState.termsEnum.postings(fromDeState.postingsEnum, PostingsEnum.NONE);
+            PostingsEnum postingsEnum = fromDeState.postingsEnum;
+  
+            if (postingsEnum instanceof MultiPostingsEnum) {
+              MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
+              int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
+              outer: for (int subindex = 0; subindex<numSubs; subindex++) {
+                MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
+                if (sub.postingsEnum == null) continue;
+                int base = sub.slice.start;
+                int docid;
+                while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+                  if (fastForRandomSet.exists(docid+base)) {
+                    intersects = true;
+                    break outer;
+                  }
+                }
+              }
+            } else {
               int docid;
-              while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
-                if (fastForRandomSet.exists(docid+base)) {
+              while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+                if (fastForRandomSet.exists(docid)) {
                   intersects = true;
-                  break outer;
+                  break;
                 }
               }
             }
           } else {
-            int docid;
-            while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
-              if (fastForRandomSet.exists(docid)) {
-                intersects = true;
-                break;
-              }
-            }
+            // use the filter cache
+            DocSet fromTermSet = fromSearcher.getDocSet(fromDeState);
+            intersects = fromSet.intersects(fromTermSet);
           }
-        } else {
-          // use the filter cache
-          DocSet fromTermSet = fromSearcher.getDocSet(fromDeState);
-          intersects = fromSet.intersects(fromTermSet);
-        }
-
-        if (intersects) {
-          fromTermHits++;
-          fromTermHitsTotalDf++;
-          TermsEnum.SeekStatus status = toTermsEnum.seekCeil(term);
-          if (status == TermsEnum.SeekStatus.END) break;
-          if (status == TermsEnum.SeekStatus.FOUND) {
-            toTermHits++;
-            int df = toTermsEnum.docFreq();
-            toTermHitsTotalDf += df;
-            if (resultBits==null && df + resultListDocs > maxSortedIntSize && resultList.size() > 0) {
-              resultBits = new FixedBitSet(toSearcher.maxDoc());
-            }
-
-            // if we don't have a bitset yet, or if the resulting set will be too large
-            // use the filterCache to get a DocSet
-            if (toTermsEnum.docFreq() >= minDocFreqTo || resultBits == null) {
-              // use filter cache
-              DocSet toTermSet = toSearcher.getDocSet(toDeState);
-              resultListDocs += toTermSet.size();
-              if (resultBits != null) {
-                toTermSet.addAllTo(new BitDocSet(resultBits));
-              } else {
-                if (toTermSet instanceof BitDocSet) {
-                  resultBits = ((BitDocSet)toTermSet).bits.clone();
+  
+          if (intersects) {
+            fromTermHits++;
+            fromTermHitsTotalDf++;
+            TermsEnum.SeekStatus status = toTermsEnum.seekCeil(term);
+            if (status == TermsEnum.SeekStatus.END) break;
+            if (status == TermsEnum.SeekStatus.FOUND) {
+              toTermHits++;
+              int df = toTermsEnum.docFreq();
+              toTermHitsTotalDf += df;
+              if (resultBits==null && df + resultListDocs > maxSortedIntSize && resultList.size() > 0) {
+                resultBits = new FixedBitSet(toSearcher.maxDoc());
+              }
+  
+              // if we don't have a bitset yet, or if the resulting set will be too large
+              // use the filterCache to get a DocSet
+              if (toTermsEnum.docFreq() >= minDocFreqTo || resultBits == null) {
+                // use filter cache
+                DocSet toTermSet = toSearcher.getDocSet(toDeState);
+                resultListDocs += toTermSet.size();
+                if (resultBits != null) {
+                  toTermSet.addAllTo(new BitDocSet(resultBits));
                 } else {
-                  resultList.add(toTermSet);
+                  if (toTermSet instanceof BitDocSet) {
+                    resultBits = ((BitDocSet)toTermSet).bits.clone();
+                  } else {
+                    resultList.add(toTermSet);
+                  }
                 }
-              }
-            } else {
-              toTermDirectCount++;
-
-              // need to use liveDocs here so we don't map to any deleted ones
-              toDeState.postingsEnum = toDeState.termsEnum.postings(toDeState.postingsEnum, PostingsEnum.NONE);
-              toDeState.postingsEnum = BitsFilteredPostingsEnum.wrap(toDeState.postingsEnum, toDeState.liveDocs);
-              PostingsEnum postingsEnum = toDeState.postingsEnum;
-
-              if (postingsEnum instanceof MultiPostingsEnum) {
-                MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
-                int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
-                for (int subindex = 0; subindex<numSubs; subindex++) {
-                  MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
-                  if (sub.postingsEnum == null) continue;
-                  int base = sub.slice.start;
+              } else {
+                toTermDirectCount++;
+  
+                // need to use liveDocs here so we don't map to any deleted ones
+                toDeState.postingsEnum = toDeState.termsEnum.postings(toDeState.postingsEnum, PostingsEnum.NONE);
+                toDeState.postingsEnum = BitsFilteredPostingsEnum.wrap(toDeState.postingsEnum, toDeState.liveDocs);
+                PostingsEnum postingsEnum = toDeState.postingsEnum;
+  
+                if (postingsEnum instanceof MultiPostingsEnum) {
+                  MultiPostingsEnum.EnumWithSlice[] subs = ((MultiPostingsEnum) postingsEnum).getSubs();
+                  int numSubs = ((MultiPostingsEnum) postingsEnum).getNumSubs();
+                  for (int subindex = 0; subindex<numSubs; subindex++) {
+                    MultiPostingsEnum.EnumWithSlice sub = subs[subindex];
+                    if (sub.postingsEnum == null) continue;
+                    int base = sub.slice.start;
+                    int docid;
+                    while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+                      resultListDocs++;
+                      resultBits.set(docid + base);
+                    }
+                  }
+                } else {
                   int docid;
-                  while ((docid = sub.postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+                  while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
                     resultListDocs++;
-                    resultBits.set(docid + base);
+                    resultBits.set(docid);
                   }
                 }
-              } else {
-                int docid;
-                while ((docid = postingsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
-                  resultListDocs++;
-                  resultBits.set(docid);
-                }
               }
+  
             }
-
           }
+  
+          term = termsEnum.next();
         }
-
-        term = termsEnum.next();
-      }
-
-      smallSetsDeferred = resultList.size();
-
-      if (resultBits != null) {
-        BitDocSet bitSet = new BitDocSet(resultBits);
+  
+        smallSetsDeferred = resultList.size();
+  
+        if (resultBits != null) {
+          BitDocSet bitSet = new BitDocSet(resultBits);
+          for (DocSet set : resultList) {
+            set.addAllTo(bitSet);
+          }
+          return bitSet;
+        }
+  
+        if (resultList.size()==0) {
+          return DocSet.EMPTY;
+        }
+  
+        if (resultList.size() == 1) {
+          return resultList.get(0);
+        }
+  
+        int sz = 0;
+  
+        for (DocSet set : resultList)
+          sz += set.size();
+  
+        int[] docs = new int[sz];
+        int pos = 0;
         for (DocSet set : resultList) {
-          set.addAllTo(bitSet);
+          System.arraycopy(((SortedIntDocSet)set).getDocs(), 0, docs, pos, set.size());
+          pos += set.size();
+        }
+        Arrays.sort(docs);
+        int[] dedup = new int[sz];
+        pos = 0;
+        int last = -1;
+        for (int doc : docs) {
+          if (doc != last)
+            dedup[pos++] = doc;
+          last = doc;
+        }
+  
+        if (pos != dedup.length) {
+          dedup = Arrays.copyOf(dedup, pos);
+        }
+  
+        return new SortedIntDocSet(dedup, dedup.length);
+        
+      } finally {
+        if (fromSearcherRef != null) {
+          fromSearcherRef.decref();
+        }
+        if (fromCore != null && openedFromCoreHere) {
+          fromCore.close();
         }
-        return bitSet;
-      }
-
-      if (resultList.size()==0) {
-        return DocSet.EMPTY;
-      }
-
-      if (resultList.size() == 1) {
-        return resultList.get(0);
-      }
-
-      int sz = 0;
-
-      for (DocSet set : resultList)
-        sz += set.size();
-
-      int[] docs = new int[sz];
-      int pos = 0;
-      for (DocSet set : resultList) {
-        System.arraycopy(((SortedIntDocSet)set).getDocs(), 0, docs, pos, set.size());
-        pos += set.size();
-      }
-      Arrays.sort(docs);
-      int[] dedup = new int[sz];
-      pos = 0;
-      int last = -1;
-      for (int doc : docs) {
-        if (doc != last)
-          dedup[pos++] = doc;
-        last = doc;
-      }
-
-      if (pos != dedup.length) {
-        dedup = Arrays.copyOf(dedup, pos);
       }
 
-      return new SortedIntDocSet(dedup, dedup.length);
     }
 
   }
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 210e0ad..23355c9 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -30,12 +31,11 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
-import com.codahale.metrics.MetricRegistry;
-import com.google.common.collect.Iterables;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.ExitableDirectoryReader;
@@ -50,8 +50,41 @@ import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermStates;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.search.*;
+import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.CollectionStatistics;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.CollectorManager;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LeafCollector;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MultiCollector;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryVisitor;
+import org.apache.lucene.search.Scorable;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TermStatistics;
+import org.apache.lucene.search.TimeLimitingCollector;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopDocsCollector;
+import org.apache.lucene.search.TopFieldCollector;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.search.TopScoreDocCollector;
+import org.apache.lucene.search.TotalHitCountCollector;
+import org.apache.lucene.search.TotalHits;
+import org.apache.lucene.search.Weight;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
@@ -59,6 +92,7 @@ import org.apache.lucene.util.FixedBitSet;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.ObjectReleaseTracker;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.DirectoryFactory.DirContext;
@@ -79,9 +113,13 @@ import org.apache.solr.search.stats.StatsSource;
 import org.apache.solr.uninverting.UninvertingReader;
 import org.apache.solr.update.IndexFingerprint;
 import org.apache.solr.update.SolrIndexConfig;
+import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.codahale.metrics.MetricRegistry;
+import com.google.common.collect.Iterables;
+
 /**
  * SolrIndexSearcher adds schema awareness and caching functionality over {@link IndexSearcher}.
  *
@@ -228,7 +266,8 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
   public SolrIndexSearcher(SolrCore core, String path, IndexSchema schema, String name, DirectoryReader r,
       boolean closeReader, boolean enableCache, boolean reserveDirectory, DirectoryFactory directoryFactory)
           throws IOException {
-    super(wrapReader(core, r));
+    super(wrapReader(core, r),  ExecutorUtil.newMDCAwareCachedThreadPool(1024,
+        new DefaultSolrThreadFactory("searcherCollectorExecutor")));
 
     this.path = path;
     this.directoryFactory = directoryFactory;
@@ -476,6 +515,15 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     if (releaseDirectory) {
       directoryFactory.release(getIndexReader().directory());
     }
+    
+    try {
+      ExecutorUtil.shutdownAndAwaitTermination(executor);
+    } catch (Throwable e) {
+      SolrException.log(log, e);
+      if (e instanceof Error) {
+        throw (Error) e;
+      }
+    }
 
     // do this at the end so it only gets done if there are no exceptions
     numCloses.incrementAndGet();
@@ -1475,7 +1523,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
 
     // if we have a cursor, then we have a sort that at minimum involves uniqueKey..
     // so we must have a TopFieldDocs containing FieldDoc[]
-    assert topDocs instanceof TopFieldDocs : "TopFieldDocs cursor constraint violated";
+    assert topDocs instanceof TopFieldDocs : "TopFieldDocs cursor constraint violated, the actual type is: "+topDocs.getClass();
     final TopFieldDocs topFieldDocs = (TopFieldDocs) topDocs;
     final ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;
 
@@ -1584,6 +1632,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
         };
       }
 
+      log.info("calling from 1"); // nocommit
       buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
       nDocsReturned = 0;
@@ -1594,24 +1643,39 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
       // no docs on this page, so cursor doesn't change
       qr.setNextCursorMark(cmd.getCursorMark());
     } else {
-      final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
-      MaxScoreCollector maxScoreCollector = null;
-      Collector collector = topCollector;
-      if ((cmd.getFlags() & GET_SCORES) != 0) {
-        maxScoreCollector = new MaxScoreCollector();
-        collector = MultiCollector.wrap(topCollector, maxScoreCollector);
+      TopDocs topDocs;
+      log.info("calling from 2, query: "+query.getClass()); // nocommit
+      if (pf.postFilter != null || cmd.getSegmentTerminateEarly() || cmd.getTimeAllowed() > 0 
+          || query instanceof RankQuery) {
+        log.debug("skipping collector manager");
+        final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
+        MaxScoreCollector maxScoreCollector = null;
+        Collector collector = topCollector;
+        if ((cmd.getFlags() & GET_SCORES) != 0) {
+          maxScoreCollector = new MaxScoreCollector();
+          collector = MultiCollector.wrap(topCollector, maxScoreCollector);
+        }
+        buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
+        totalHits = topCollector.getTotalHits();
+        topDocs = topCollector.topDocs(0, len);
+
+        maxScore = totalHits > 0 ? (maxScoreCollector == null ? Float.NaN : maxScoreCollector.getMaxScore()) : 0.0f;
+        nDocsReturned = topDocs.scoreDocs.length;
+      } else {
+        log.debug("using collectormanager");
+        CollectorManagerResult result = searchCollectorManagers(len, cmd, query, true, true, false); // nocommit: need docset should be false
+        totalHits = result.totalHits;
+
+        maxScore = result.maxScore;
+        nDocsReturned = result.topDocs.scoreDocs.length;
+        topDocs = result.topDocs;
       }
-      buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
-      totalHits = topCollector.getTotalHits();
-      TopDocs topDocs = topCollector.topDocs(0, len);
       if (cmd.getSort() != null && query instanceof RankQuery == false && (cmd.getFlags() & GET_SCORES) != 0) {
         TopFieldCollector.populateScores(topDocs.scoreDocs, this, query);
       }
       populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
 
-      maxScore = totalHits > 0 ? (maxScoreCollector == null ? Float.NaN : maxScoreCollector.getMaxScore()) : 0.0f;
-      nDocsReturned = topDocs.scoreDocs.length;
       ids = new int[nDocsReturned];
       scores = (cmd.getFlags() & GET_SCORES) != 0 ? new float[nDocsReturned] : null;
       for (int i = 0; i < nDocsReturned; i++) {
@@ -1625,6 +1689,80 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
     if (sliceLen < 0) sliceLen = 0;
     qr.setDocList(new DocSlice(0, sliceLen, ids, scores, totalHits, maxScore));
   }
+  
+  CollectorManagerResult searchCollectorManagers(int len, QueryCommand cmd, Query query,
+      boolean needTopDocs, boolean needMaxScore, boolean needDocSet) throws IOException {
+    CollectorManager<MultiCollector, CollectorManagerResult> manager = new CollectorManager<MultiCollector, CollectorManagerResult>() {
+      @Override
+      public MultiCollector newCollector() throws IOException {
+        Collection<Collector> collectors = new ArrayList<Collector>();
+        if (needTopDocs) collectors.add(buildTopDocsCollector(len, cmd));
+        if (needMaxScore) collectors.add(new MaxScoreCollector());
+        if (needDocSet) collectors.add(new DocSetCollector(maxDoc()));
+        return (MultiCollector) MultiCollector.wrap(collectors);
+      }
+
+      @Override
+      public CollectorManagerResult reduce(Collection<MultiCollector> multiCollectors) throws IOException {
+        final TopDocs[] topDocs = new TopDocs[multiCollectors.size()];
+        float maxScore = 0.0f;
+        DocSet docSet = new BitDocSet(new FixedBitSet(maxDoc())); // TODO: if docset is not needed, avoid this initialization
+        int i = 0;
+        for (MultiCollector multiCollector: multiCollectors) {
+          int c = 0;
+          List<Collector> subCollectors = multiCollector.getCollectors();
+          TopDocsCollector topDocsCollector = needTopDocs? ((TopDocsCollector) subCollectors.get(c++)): null;
+          MaxScoreCollector maxScoreCollector = needMaxScore? ((MaxScoreCollector) subCollectors.get(c++)): null;
+          DocSetCollector docSetCollector = needDocSet? ((DocSetCollector) subCollectors.get(c++)): null;
+          
+          if (needTopDocs)  topDocs[i++] = (topDocsCollector instanceof TopFieldCollector)? ((TopFieldCollector)topDocsCollector).topDocs(0, len): topDocsCollector.topDocs(0, len);
+          if (needMaxScore) 
+            if (!Float.isNaN(maxScoreCollector.getMaxScore()))
+              maxScore = Math.max(maxScore, maxScoreCollector.getMaxScore());
+          if (needDocSet)   docSet = docSet.union(docSetCollector.getDocSet());
+        }
+        TopDocs mergedTopDocs;
+        if (topDocs != null && topDocs.length>0 && topDocs[0] instanceof TopFieldDocs) {
+          TopFieldDocs[] topFieldDocs = Arrays.copyOf(topDocs, topDocs.length, TopFieldDocs[].class);
+          mergedTopDocs = TopFieldDocs.merge(weightSort(cmd.getSort()), len, topFieldDocs);
+        } else {
+          mergedTopDocs = needTopDocs? TopDocs.merge(0, len, topDocs, true): null;
+        }
+        int totalHits = needTopDocs? (int)mergedTopDocs.totalHits.value: -1;
+        maxScore = totalHits > 0 ? maxScore : 0.0f;
+        return new CollectorManagerResult(mergedTopDocs, docSet, maxScore, totalHits);
+      }
+
+    };
+
+    CollectorManagerResult ret;
+    try {
+      ret = super.search(query, manager);
+    } catch (Exception ex) {
+      if (ex instanceof RuntimeException && 
+          ex.getCause() != null & ex.getCause() instanceof ExecutionException
+          && ex.getCause().getCause() != null && ex.getCause().getCause() instanceof RuntimeException) {
+        throw (RuntimeException)ex.getCause().getCause();
+      } else {
+        throw ex;
+      }
+    }
+    return ret;
+  }
+
+  class CollectorManagerResult {
+    TopDocs topDocs;
+    DocSet docSet;
+    float maxScore;
+    int totalHits;
+    
+    public CollectorManagerResult(TopDocs topDocs, DocSet docSet, float maxScore, int totalHits) {
+      this.topDocs = topDocs;
+      this.docSet = docSet;
+      this.maxScore = maxScore;
+      this.totalHits = totalHits;
+    }
+  }
 
   // any DocSet returned is for the query only, without any filtering... that way it may
   // be cached if desired.
@@ -1682,7 +1820,7 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
 
         collector = MultiCollector.wrap(setCollector, topScoreCollector);
       }
-
+      log.info("calling from 3");  // nocommit
       buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
       set = DocSetUtil.getDocSet(setCollector, this);
@@ -1695,33 +1833,46 @@ public class SolrIndexSearcher extends IndexSearcher implements Closeable, SolrI
       // no docs on this page, so cursor doesn't change
       qr.setNextCursorMark(cmd.getCursorMark());
     } else {
+      TopDocs topDocs;
+      if (pf.postFilter != null || cmd.getSegmentTerminateEarly() || cmd.getTimeAllowed() > 0 || query instanceof RankQuery) {
 
-      final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
-      DocSetCollector setCollector = new DocSetCollector(maxDoc);
-      MaxScoreCollector maxScoreCollector = null;
-      List<Collector> collectors = new ArrayList<>(Arrays.asList(topCollector, setCollector));
+        final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
+        DocSetCollector setCollector = new DocSetCollector(maxDoc);
+        MaxScoreCollector maxScoreCollector = null;
+        List<Collector> collectors = new ArrayList<>(Arrays.asList(topCollector, setCollector));
 
-      if ((cmd.getFlags() & GET_SCORES) != 0) {
-        maxScoreCollector = new MaxScoreCollector();
-        collectors.add(maxScoreCollector);
-      }
+        if ((cmd.getFlags() & GET_SCORES) != 0) {
+          maxScoreCollector = new MaxScoreCollector();
+          collectors.add(maxScoreCollector);
+        }
 
-      Collector collector = MultiCollector.wrap(collectors);
+        Collector collector = MultiCollector.wrap(collectors);
 
-      buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
+        log.info("calling from 4");  // nocommit
+        buildAndRunCollectorChain(qr, query, collector, cmd, pf.postFilter);
 
-      set = DocSetUtil.getDocSet(setCollector, this);
+        set = DocSetUtil.getDocSet(setCollector, this);
+        totalHits = topCollector.getTotalHits();
+        assert (totalHits == set.size()) || qr.isPartialResults();
+        topDocs = topCollector.topDocs(0, len);
+        maxScore = totalHits > 0 ? (maxScoreCollector == null ? Float.NaN : maxScoreCollector.getMaxScore()) : 0.0f;
+        nDocsReturned = topDocs.scoreDocs.length;
 
-      totalHits = topCollector.getTotalHits();
-      assert (totalHits == set.size()) || qr.isPartialResults();
+      } else {
+        log.debug("using collectormanager");
+        CollectorManagerResult result = searchCollectorManagers(len, cmd, query, true, true, true);
+        set = result.docSet;
+        totalHits = result.totalHits;
+        assert (totalHits == set.size()) || qr.isPartialResults();
+        topDocs = result.topDocs;
+        maxScore = result.maxScore;
+        nDocsReturned = result.topDocs.scoreDocs.length;
+      }
 
-      TopDocs topDocs = topCollector.topDocs(0, len);
       if (cmd.getSort() != null && query instanceof RankQuery == false && (cmd.getFlags() & GET_SCORES) != 0) {
         TopFieldCollector.populateScores(topDocs.scoreDocs, this, query);
       }
       populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
-      maxScore = totalHits > 0 ? (maxScoreCollector == null ? Float.NaN : maxScoreCollector.getMaxScore()) : 0.0f;
-      nDocsReturned = topDocs.scoreDocs.length;
 
       ids = new int[nDocsReturned];
       scores = (cmd.getFlags() & GET_SCORES) != 0 ? new float[nDocsReturned] : null;
diff --git a/solr/core/src/test/org/apache/solr/schema/TestOmitPositions.java b/solr/core/src/test/org/apache/solr/schema/TestOmitPositions.java
index 0fcc9fe..c672d68 100644
--- a/solr/core/src/test/org/apache/solr/schema/TestOmitPositions.java
+++ b/solr/core/src/test/org/apache/solr/schema/TestOmitPositions.java
@@ -51,7 +51,8 @@ public class TestOmitPositions extends SolrTestCaseJ4 {
               "//*[@numFound='0']"
     );
     } catch (Exception expected) {
-      assertTrue(expected.getCause() instanceof IllegalStateException);
+      assertTrue(expected.getCause() instanceof IllegalStateException ||
+          expected.getCause().getCause().getCause() instanceof IllegalStateException);
       // in lucene 4.0, queries don't silently fail
     }
     resetExceptionIgnores();
diff --git a/solr/core/src/test/org/apache/solr/search/TestFiltering.java b/solr/core/src/test/org/apache/solr/search/TestFiltering.java
index b2ad9cf..4114595 100644
--- a/solr/core/src/test/org/apache/solr/search/TestFiltering.java
+++ b/solr/core/src/test/org/apache/solr/search/TestFiltering.java
@@ -77,7 +77,7 @@ public class TestFiltering extends SolrTestCaseJ4 {
         if (live == null) {
           live = searcher.getLiveDocSet();
         }
-        assertTrue( set == live);
+        assertTrue( set.equals(live));
 
         QueryCommand cmd = new QueryCommand();
         cmd.setQuery( QParser.getParser(qstr, null, req).getQuery() );
@@ -86,14 +86,14 @@ public class TestFiltering extends SolrTestCaseJ4 {
         QueryResult res = new QueryResult();
         searcher.search(res, cmd);
         set = res.getDocSet();
-        assertTrue( set == live );
+        assertTrue( set.equals(live) );
 
         cmd.setQuery( QParser.getParser(qstr + " OR id:0", null, req).getQuery() );
         cmd.setFilterList( QParser.getParser(qstr + " OR id:1", null, req).getQuery() );
         res = new QueryResult();
         searcher.search(res, cmd);
         set = res.getDocSet();
-        assertTrue( set == live );
+        assertTrue( set.equals(live) );
       }
 
     } finally {