SOLR-13509: add omitHeader=false for shards requests to avoid NPE on partialResuls check

This commit is contained in:
Mikhail Khludnev 2019-06-12 17:53:16 +02:00
parent 608d9134ad
commit 5ba6c0c7a2
7 changed files with 68 additions and 32 deletions

View File

@ -135,6 +135,8 @@ Bug Fixes
* SOLR-12013: collections API CUSTERSTATUS command fails when configset missing (Erick Erickson)
* SOLR-13509: NPE on omitHeader=true is fixed by sending omitHeader=false to shard searches (Munendra S N, Mikhail Khludnev)
Other Changes
----------------------

View File

@ -849,7 +849,7 @@ public class QueryComponent extends SearchComponent
}
else {
responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
final Object rhste = (responseHeader == null ? null : responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_SEGMENT_TERMINATED_EARLY_KEY));
final Object rhste = responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_SEGMENT_TERMINATED_EARLY_KEY);
if (rhste != null) {
nl.add(SolrQueryResponse.RESPONSE_HEADER_SEGMENT_TERMINATED_EARLY_KEY, rhste);
}
@ -879,20 +879,16 @@ public class QueryComponent extends SearchComponent
}
final boolean thisResponseIsPartial;
if (responseHeader != null) {
thisResponseIsPartial = Boolean.TRUE.equals(responseHeader.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY));
thereArePartialResults |= thisResponseIsPartial;
if (!Boolean.TRUE.equals(segmentTerminatedEarly)) {
final Object ste = responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_SEGMENT_TERMINATED_EARLY_KEY);
if (Boolean.TRUE.equals(ste)) {
segmentTerminatedEarly = Boolean.TRUE;
} else if (Boolean.FALSE.equals(ste)) {
segmentTerminatedEarly = Boolean.FALSE;
}
thisResponseIsPartial = Boolean.TRUE.equals(responseHeader.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY));
thereArePartialResults |= thisResponseIsPartial;
if (!Boolean.TRUE.equals(segmentTerminatedEarly)) {
final Object ste = responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_SEGMENT_TERMINATED_EARLY_KEY);
if (Boolean.TRUE.equals(ste)) {
segmentTerminatedEarly = Boolean.TRUE;
} else if (Boolean.FALSE.equals(ste)) {
segmentTerminatedEarly = Boolean.FALSE;
}
} else {
thisResponseIsPartial = false;
}
// calculate global maxScore and numDocsFound
@ -1185,7 +1181,7 @@ public class QueryComponent extends SearchComponent
}
{
NamedList<?> responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
if (responseHeader!=null && Boolean.TRUE.equals(responseHeader.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
if (Boolean.TRUE.equals(responseHeader.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
rb.rsp.getResponseHeader().asShallowMap()
.put(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE);
}

View File

@ -16,9 +16,6 @@
*/
package org.apache.solr.handler.component;
import static org.apache.solr.common.params.CommonParams.DISTRIB;
import static org.apache.solr.common.params.CommonParams.PATH;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.MethodHandles;
@ -56,6 +53,9 @@ import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CommonParams.DISTRIB;
import static org.apache.solr.common.params.CommonParams.PATH;
/**
*
@ -370,6 +370,7 @@ public class SearchHandler extends RequestHandlerBase implements SolrCoreAware ,
params.set(ShardParams.IS_SHARD, true); // a sub (shard) request
params.set(ShardParams.SHARDS_PURPOSE, sreq.purpose);
params.set(ShardParams.SHARD_URL, shard); // so the shard knows what was asked
params.set(CommonParams.OMIT_HEADER, false);
if (rb.requestInfo != null) {
// we could try and detect when this is needed, but it could be tricky
params.set("NOW", Long.toString(rb.requestInfo.getNOW().getTime()));

View File

@ -21,13 +21,15 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.codahale.metrics.Metered;
import com.codahale.metrics.MetricRegistry;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.MiniSolrCloudCluster.JettySolrRunnerWithMetrics;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.*;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
@ -40,9 +42,11 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.codahale.metrics.Metered;
import com.codahale.metrics.MetricRegistry;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.CheckMethodName;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.Trap;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.catchClass;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.catchCount;
import static org.apache.solr.cloud.TrollingIndexReaderFactory.catchTrace;
/**
* Distributed test for {@link org.apache.lucene.index.ExitableDirectoryReader}
@ -193,7 +197,7 @@ public class CloudExitableDirectoryReaderTest extends SolrCloudTestCase {
params( "sort","query($q,1) asc"),
params("rows","0", "facet","true", "facet.method", "enum", "facet.field", "name"),
params("rows","0", "json.facet","{ ids: { type: range, field : num, start : 1, end : 99, gap : 9 }}")
}; //add more cases here
}; // add more cases here
params.add(cases[random().nextInt(cases.length)]);
for (; ; creep*=1.5) {
@ -218,13 +222,20 @@ public class CloudExitableDirectoryReaderTest extends SolrCloudTestCase {
int numBites = atLeast(100);
for(int bite=0; bite<numBites; bite++) {
int boundary = random().nextInt(creep);
boolean omitHeader = random().nextBoolean();
try(Trap catchCount = catchCount(boundary)){
params.set("omitHeader", "" + omitHeader);
params.set("boundary", boundary);
QueryResponse rsp = cluster.getSolrClient().query(COLLECTION,
params);
assertEquals(""+rsp, rsp.getStatus(), 0);
assertNo500s(""+rsp);
assertEquals(""+creep+" ticks were sucessful; trying "+boundary+" yields "+rsp,
// without responseHeader, whether the response is partial or not can't be known
// omitHeader=true used in request to ensure that no NPE exceptions are thrown
if (omitHeader) {
continue;
}
assertEquals("" + creep + " ticks were successful; trying " + boundary + " yields " + rsp,
catchCount.hasCaught(), isPartial(rsp));
}catch(AssertionError ae) {
Trap.dumpLastStackTraces(log);

View File

@ -117,6 +117,10 @@ public class DistributedDebugComponentTest extends SolrJettyTestBase {
query.set("distrib", "true");
query.setFields("id", "text");
query.set("shards", shard1 + "," + shard2);
if (random().nextBoolean()) {
query.add("omitHeader", Boolean.toString(random().nextBoolean()));
}
QueryResponse response = collection1.query(query);
NamedList<Object> track = (NamedList<Object>) response.getDebugMap().get("track");
assertNotNull(track);
@ -138,13 +142,6 @@ public class DistributedDebugComponentTest extends SolrJettyTestBase {
assertElementsPresent((NamedList<String>)((NamedList<Object>)track.get("GET_FIELDS")).get(shard2),
"QTime", "ElapsedTime", "RequestPurpose", "NumFound", "Response");
query.add("omitHeader", "true");
response = collection1.query(query);
assertNull("QTime is not included in the response when omitHeader is set to true",
((NamedList<Object>)response.getDebugMap().get("track")).findRecursive("EXECUTE_QUERY", shard1, "QTime"));
assertNull("QTime is not included in the response when omitHeader is set to true",
((NamedList<Object>)response.getDebugMap().get("track")).findRecursive("GET_FIELDS", shard2, "QTime"));
query.setQuery("id:1");
response = collection1.query(query);
track = (NamedList<Object>) response.getDebugMap().get("track");

View File

@ -219,6 +219,15 @@ public class RangeFacetCloudTest extends SolrCloudTestCase {
}
}
}
public void testStatsWithOmitHeader() throws Exception {
// SOLR-13509: no NPE should be thrown when only stats are specified with omitHeader=true
SolrQuery solrQuery = new SolrQuery("q", "*:*", "omitHeader", "true",
"json.facet", "{unique_foo:\"unique(" + STR_FIELD+ ")\"}");
final QueryResponse rsp = cluster.getSolrClient().query(solrQuery);
// response shouldn't contain header as omitHeader is set to true
assertNull(rsp.getResponseHeader());
}
public void testInclude_Upper() throws Exception {
for (boolean doSubFacet : Arrays.asList(false, true)) {

View File

@ -203,7 +203,27 @@ The default value of this parameter is blank, which causes no extra "explain inf
== timeAllowed Parameter
This parameter specifies the amount of time, in milliseconds, allowed for a search to complete. If this time expires before the search is complete, any partial results will be returned, but values such as `numFound`, <<faceting.adoc#faceting,facet>> counts, and result <<the-stats-component.adoc#the-stats-component,stats>> may not be accurate for the entire result set.
This parameter specifies the amount of time, in milliseconds, allowed for a search to complete. If this time expires before the search is complete, any partial results will be returned, but values such as `numFound`, <<faceting.adoc#faceting,facet>> counts, and result <<the-stats-component.adoc#the-stats-component,stats>> may not be accurate for the entire result set. In case of expiration, if `omitHeader` isn't set to `true` the response header contains a special flag called `partialResults`.
[source,json]
----
{
"responseHeader": {
"status": 0,
"zkConnected": true,
"partialResults": true,
"QTime": 20,
"params": {
"q": "*:*"
}
},
"response": {
"numFound": 77,
"start": 0,
"docs": [ "..." ]
}
}
----
This value is only checked at the time of: