You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@lucene.apache.org by "Ron Ben-Yosef (JIRA)" <ji...@apache.org> on 2018/03/12 09:29:00 UTC

[jira] [Comment Edited] (SOLR-8291) NPE calling export handler when useFilterForSortedQuery=true

    [ https://issues.apache.org/jira/browse/SOLR-8291?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16378465#comment-16378465 ] 

Ron Ben-Yosef edited comment on SOLR-8291 at 3/12/18 9:28 AM:
--------------------------------------------------------------

Hi,

I've run into the same issue, or at least what I think is the same issue (I'm on 7.2.1, and the call stack leading up to the exception looks somewhat different, but reading everything written here I'm fairly sure it's the same issue. If it's not - let me know and I can open a separate bug for it).

The scenario is similar - querying the /export handler with the "useFilterForSortedQuery" configuration option enabled.

Here's the call stack leading up to the exception:

o.a.s.s.HttpSolrCall null:java.lang.NullPointerException
 at org.apache.lucene.util.BitSetIterator.<init>(BitSetIterator.java:61)
 at org.apache.solr.handler.ExportWriter.writeDocs(ExportWriter.java:243)
 at org.apache.solr.handler.ExportWriter.lambda$null$1(ExportWriter.java:222)
 at org.apache.solr.response.JSONWriter.writeIterator(JSONResponseWriter.java:523)
 at org.apache.solr.response.TextResponseWriter.writeVal(TextResponseWriter.java:180)
 at org.apache.solr.response.JSONWriter$2.put(JSONResponseWriter.java:559)
 at org.apache.solr.handler.ExportWriter.lambda$null$2(ExportWriter.java:222)
 at org.apache.solr.response.JSONWriter.writeMap(JSONResponseWriter.java:547)
 at org.apache.solr.response.TextResponseWriter.writeVal(TextResponseWriter.java:198)
 at org.apache.solr.response.JSONWriter$2.put(JSONResponseWriter.java:559)
 at org.apache.solr.handler.ExportWriter.lambda$write$3(ExportWriter.java:220)
 at org.apache.solr.response.JSONWriter.writeMap(JSONResponseWriter.java:547)
 at org.apache.solr.handler.ExportWriter.write(ExportWriter.java:218)
 at org.apache.solr.core.SolrCore$3.write(SolrCore.java:2627)
 at org.apache.solr.response.QueryResponseWriterUtil.writeQueryResponse(QueryResponseWriterUtil.java:49)
 at org.apache.solr.servlet.HttpSolrCall.writeResponse(HttpSolrCall.java:788)
 at org.apache.solr.servlet.HttpSolrCall.call(HttpSolrCall.java:525)
 at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:382)
 at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:326)
 at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1751)
 at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
 at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
 at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
 at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
 at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
 at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
 at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
 at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
 at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
 at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213)
 at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:119)
 at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
 at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:335)
 at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
 at org.eclipse.jetty.server.Server.handle(Server.java:534)
 at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
 at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
 at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:283)
 at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:108)
 at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
 at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
 at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
 at java.lang.Thread.run(Thread.java:748)

 

Debugging the issue, it seems what's happening is that when there's more than one leaf (=segment?) then the per-leaf bitsets are being created only upto the highest leaf with matching documents, and if that's not the last leaf then you're going to have null bitsets for all the leaves higher than the highest one containing a matching document. This happens in SolrIndexSearcher.java in the function sortDocSet:
{code:java}
...
LeafCollector leafCollector = null;
while (iter.hasNext()) {
 int doc = iter.nextDoc();
 while (doc >= end) {
  LeafReaderContext leaf = leafContexts.get(readerIndex++);
  base = leaf.docBase;
  end = base + leaf.reader().maxDoc();
  leafCollector = topCollector.getLeafCollector(leaf);
  // we should never need to set the scorer given the settings for the collector
 }
 leafCollector.collect(doc - base);
}
...{code}
As can be seen in the code above, once there are no more matching documents the loop won't be entered anymore and so the function getLeafCollector will not be called for the remaining leaves. Since that is where ExportCollector sets the leaves' bitsets, the ones for which the function is not called will not get set and remain null, as can be seen in ExportQParserPlugin.java:
{code:java}
@Override
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
 final FixedBitSet set = new FixedBitSet(context.reader().maxDoc());
 this.sets[context.ord] = set;
 return new LeafCollector() {
 
  @Override
  public void setScorer(Scorer scorer) throws IOException {}

  @Override
  public void collect(int docId) throws IOException{
   ++totalHits;
   set.set(docId);
  }
 };
}{code}
Anyhow, this array of bitsets eventually gets propagated and ends up at the ExportWriter's writeDocs method where the bitsets are used to create instances of BitSetIterator, the constructor of which then throws the NPE when trying to call the length method on the bitset in the case that it's null:
{code:java}
...
for(int i=0; i<leaves.size(); i++) {
 sortDoc.setNextReader(leaves.get(i));
 DocIdSetIterator it = new BitSetIterator(sets[i], 0); // cost is not useful here
 int docId = -1;
 while((docId = it.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
  sortDoc.setValues(docId);
  if(top.lessThan(sortDoc)) {
   top.setValues(sortDoc);
   top = queue.updateTop();
  }
 }
}
...{code}
 

As for a fix - I tried two things, both of which seem to work:
 * One option is simply to not try iterate the null bitsets (their corresponding leaves don't include matching documents anyway, which is why the weren't set in the first place) , for instance by modifying the code from above (from ExportWriter::writeDocs) like this:

{code:java}
...
for(int i=0; i<leaves.size(); i++) {
 if (sets[i] == null) continue;
 sortDoc.setNextReader(leaves.get(i));
 DocIdSetIterator it = new BitSetIterator(sets[i], 0); // cost is not useful here
 int docId = -1;
 while((docId = it.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
  sortDoc.setValues(docId);
  if(top.lessThan(sortDoc)) {
   top.setValues(sortDoc);
   top = queue.updateTop();
  }
 }
}
...{code}
 * The second option is to modify SolrIndexSearcher::sortDocSet to create bitsets for the remaining leaves so that they're not null, for instance by calling getLeafCollector for them as well (even though there's no need for the collector since they include no matching documents) just for the bitsets to get created. For example:

{code:java}
...
LeafCollector leafCollector = null;
while (iter.hasNext()) {
 int doc = iter.nextDoc();
 while (doc >= end) {
  LeafReaderContext leaf = leafContexts.get(readerIndex++);
  base = leaf.docBase;
  end = base + leaf.reader().maxDoc();
  leafCollector = topCollector.getLeafCollector(leaf);
  // we should never need to set the scorer given the settings for the collector
 }
 leafCollector.collect(doc - base);
}

for (int leaves = leafContexts.size(); readerIndex < leaves; readerIndex++) {
 topCollector.getLeafCollector(leafContexts.get(readerIndex));
}
...{code}
 

The first option of course won't solve any other flows where there might be attempts to access the null bitsets, but I don't know if there are any such flows or if it's at all relevant...

 

Anyway, I don't really know how to go about fixing it, have never done it before. What do I need to do? Make a fork on github and then submit a pull request?

 

Sorry for the long comment.

 

Thanks,

Ron


was (Author: ronnnnnnn):
Hi,

I've run into the same issue, or at least what I think is the same issue (I'm on 7.2.1, and the call stack leading up to the exception looks somewhat different, but reading everything written here I'm fairly sure it's the same issue. If it's not - let me know and I can open a separate bug for it).

The scenario is similar - querying the /export handler with the "useFilterForSortedQuery" configuration option enabled.

Here's the call stack leading up to the exception:

o.a.s.s.HttpSolrCall null:java.lang.NullPointerException
 at org.apache.lucene.util.BitSetIterator.<init>(BitSetIterator.java:61)
 at org.apache.solr.handler.ExportWriter.writeDocs(ExportWriter.java:243)
 at org.apache.solr.handler.ExportWriter.lambda$null$1(ExportWriter.java:222)
 at org.apache.solr.response.JSONWriter.writeIterator(JSONResponseWriter.java:523)
 at org.apache.solr.response.TextResponseWriter.writeVal(TextResponseWriter.java:180)
 at org.apache.solr.response.JSONWriter$2.put(JSONResponseWriter.java:559)
 at org.apache.solr.handler.ExportWriter.lambda$null$2(ExportWriter.java:222)
 at org.apache.solr.response.JSONWriter.writeMap(JSONResponseWriter.java:547)
 at org.apache.solr.response.TextResponseWriter.writeVal(TextResponseWriter.java:198)
 at org.apache.solr.response.JSONWriter$2.put(JSONResponseWriter.java:559)
 at org.apache.solr.handler.ExportWriter.lambda$write$3(ExportWriter.java:220)
 at org.apache.solr.response.JSONWriter.writeMap(JSONResponseWriter.java:547)
 at org.apache.solr.handler.ExportWriter.write(ExportWriter.java:218)
 at org.apache.solr.core.SolrCore$3.write(SolrCore.java:2627)
 at org.apache.solr.response.QueryResponseWriterUtil.writeQueryResponse(QueryResponseWriterUtil.java:49)
 at org.apache.solr.servlet.HttpSolrCall.writeResponse(HttpSolrCall.java:788)
 at org.apache.solr.servlet.HttpSolrCall.call(HttpSolrCall.java:525)
 at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:382)
 at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:326)
 at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1751)
 at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
 at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
 at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
 at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
 at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
 at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
 at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
 at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
 at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
 at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213)
 at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:119)
 at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
 at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:335)
 at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
 at org.eclipse.jetty.server.Server.handle(Server.java:534)
 at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
 at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
 at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:283)
 at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:108)
 at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
 at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
 at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
 at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
 at java.lang.Thread.run(Thread.java:748)

 

Debugging the issue, it seems what's happening is that when there's more than one leaf (=segment?) then the per-leaf bitsets are being created only upto the highest leaf with matching documents, and if that's not the last leaf then you're going to have null bitsets for all the leaves higher than the highest one containing a matching document. This happens in SolrIndexSearcher.java in the function sortDocSet:
{code:java}
...
LeafCollector leafCollector = null;
while (iter.hasNext()) {
 int doc = iter.nextDoc();
 while (doc >= end) {
  LeafReaderContext leaf = leafContexts.get(readerIndex++);
  base = leaf.docBase;
  end = base + leaf.reader().maxDoc();
  leafCollector = topCollector.getLeafCollector(leaf);
  // we should never need to set the scorer given the settings for the collector
 }
 leafCollector.collect(doc - base);
}
...{code}
As can be seen in the code above, once there are no more matching documents the loop won't be entered anymore and so the function getLeafCollector will not be called for the remaining leaves. Since that is where ExportCollector sets the leaves' bitsets, the ones for which the function is not called will nor get set and remain null, as can be seen in ExportQParserPlugin.java:
{code:java}
@Override
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
 final FixedBitSet set = new FixedBitSet(context.reader().maxDoc());
 this.sets[context.ord] = set;
 return new LeafCollector() {
 
  @Override
  public void setScorer(Scorer scorer) throws IOException {}

  @Override
  public void collect(int docId) throws IOException{
   ++totalHits;
   set.set(docId);
  }
 };
}{code}
Anyhow, this array of bitsets eventually gets propagated and ends up at the ExportWriter's writeDocs method where the bitsets are used to create instances of BitSetIterator, the constructor of which then throws the NPE when trying to call the length method on the bitset in the case that it's null:
{code:java}
...
for(int i=0; i<leaves.size(); i++) {
 sortDoc.setNextReader(leaves.get(i));
 DocIdSetIterator it = new BitSetIterator(sets[i], 0); // cost is not useful here
 int docId = -1;
 while((docId = it.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
  sortDoc.setValues(docId);
  if(top.lessThan(sortDoc)) {
   top.setValues(sortDoc);
   top = queue.updateTop();
  }
 }
}
...{code}
 

As for a fix - I tried two things, both of which seem to work:
 * One option is simply to not try iterate the null bitsets (their corresponding leaves don't include matching documents anyway, which is why the weren't set in the first place) , for instance by modifying the code from above (from ExportWriter::writeDocs) like this:

{code:java}
...
for(int i=0; i<leaves.size(); i++) {
 if (sets[i] == null) continue;
 sortDoc.setNextReader(leaves.get(i));
 DocIdSetIterator it = new BitSetIterator(sets[i], 0); // cost is not useful here
 int docId = -1;
 while((docId = it.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
  sortDoc.setValues(docId);
  if(top.lessThan(sortDoc)) {
   top.setValues(sortDoc);
   top = queue.updateTop();
  }
 }
}
...{code}
 * The second option is to modify SolrIndexSearcher::sortDocSet to create bitsets for the remaining leaves so that they're not null, for instance by calling getLeafCollector for them as well (even though there's no need for the collector since they include no matching documents) just for the bitsets to get created. For example:

{code:java}
...
LeafCollector leafCollector = null;
while (iter.hasNext()) {
 int doc = iter.nextDoc();
 while (doc >= end) {
  LeafReaderContext leaf = leafContexts.get(readerIndex++);
  base = leaf.docBase;
  end = base + leaf.reader().maxDoc();
  leafCollector = topCollector.getLeafCollector(leaf);
  // we should never need to set the scorer given the settings for the collector
 }
 leafCollector.collect(doc - base);
}

for (int leaves = leafContexts.size(); readerIndex < leaves; readerIndex++) {
 topCollector.getLeafCollector(leafContexts.get(readerIndex));
}
...{code}
 

The first option of course won't solve any other flows where there might be attempts to access the null bitsets, but I don't know if there are any such flows or if it's at all relevant...

 

Anyway, I don't really know how to go about fixing it, have never done it before. What do I need to do? Make a fork on github and then submit a pull request?

 

Sorry for the long comment.

 

Thanks,

Ron

> NPE calling export handler when useFilterForSortedQuery=true
> ------------------------------------------------------------
>
>                 Key: SOLR-8291
>                 URL: https://issues.apache.org/jira/browse/SOLR-8291
>             Project: Solr
>          Issue Type: Bug
>          Components: SolrCloud
>    Affects Versions: 5.2.1
>            Reporter: Ray
>            Priority: Major
>         Attachments: solr.log
>
>
> *Updated*: The stacktrace below was created when the solrconfig.xml has the following element:
> {code}
>  <useFilterForSortedQuery>true</useFilterForSortedQuery>
> {code}
> It was determined that useFilterForSortedQuery is incompatible with the /export handler.
> See the comments near the end of the ticket for a potential work around if this flag needs to be set.
> Get NPE during calling export handler, here is the stack trace:
> 	at org.apache.lucene.util.BitSetIterator.<init>(BitSetIterator.java:58)
> 	at org.apache.solr.response.SortingResponseWriter.write(SortingResponseWriter.java:138)
> 	at org.apache.solr.response.QueryResponseWriterUtil.writeQueryResponse(QueryResponseWriterUtil.java:53)
> 	at org.apache.solr.servlet.HttpSolrCall.writeResponse(HttpSolrCall.java:727)
> 	at org.apache.solr.servlet.HttpSolrCall.call(HttpSolrCall.java:459)
> 	at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:227)
> 	at org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:196)
> 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274)
> 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242)
> 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274)
> 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242)
> 	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275)
> 	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161)
> 	at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181)
> 	at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285)
> 	at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261)
> 	at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88)
> 	at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100)
> 	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:159)
> 	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
> 	at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
> 	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:567)
> 	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
> 	at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53)
> 	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362)
> 	at org.apache.coyote.ajp.AjpAprProcessor.process(AjpAprProcessor.java:489)
> 	at org.apache.coyote.ajp.AjpAprProtocol$AjpConnectionHandler.process(AjpAprProtocol.java:452)
> 	at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:2019)
> 	at java.lang.Thread.run(Thread.java:745)
> It seems there are some FixedBitSet was set to null



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@lucene.apache.org
For additional commands, e-mail: dev-help@lucene.apache.org