TL;DR: disable lazy-loading on the /spell
handler if you’re using Solr 3.6.2 and distributed (i.e. sharded) search.
I discovered an interesting bug in Solr 3.6.2 the other day, so I thought I’d share it here.
While upgrading a distributed Solr system from version 3.5.0 to 3.6.2, everything worked as expected with minimal changes to the configuration, except for the /spell
search handler.
The other search handlers I’d defined (the standard /select
and a /suggest
for autosuggestions) responded just fine, but a distributed /spell
failed with a mysterious error in the logs:
Server returned HTTP response code: 400 for URL: http://localhost:8080/solr-search/shard2/spell ?shards=localhost%3A8080%2Fsolr-search%2Fshard1%2Clocalhost%3A8080%2Fsolr-search%2Fshard2 &shards.qt=%2Fspell[...]
The logs from the server side weren’t any more helpful:
SEVERE: org.apache.solr.common.SolrException: Bad Request Bad Request request: http://localhost:8080/solr-search/shard1/spell at org.apache.solr.client.solrj.impl.CommonsHttpSolrServer.request(CommonsHttpSolrServer.java:427) at org.apache.solr.client.solrj.impl.CommonsHttpSolrServer.request(CommonsHttpSolrServer.java:249) at org.apache.solr.handler.component.HttpShardHandler$1.call(HttpShardHandler.java:129) at org.apache.solr.handler.component.HttpShardHandler$1.call(HttpShardHandler.java:103) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) at java.lang.Thread.run(Thread.java:680)
Running Wireshark to capture HTTP requests on localhost dug up the following silent error message from Tomcat:
HTTP Status 400 - isShard is only acceptable with search handlers type: Status report message: isShard is only acceptable with search handlers description: The request sent by the client was syntactically incorrect (isShard is only acceptable with search handlers).
Huh! I could have sworn it was a SearchHandler. Checking the configuration for the /spell
handler in solrconfig.xml
, I indeed found the following:
<requestHandler name="/spell" class="solr.SearchHandler" lazy="true"> ... </requestHandler>
After some fruitless fiddling with the configuration, I finally gave up and launched a remote debugger in Eclipse. Grepping the Solr 3.6.2 source code showed that the exception was thrown from SolrCore.java lines 1373-1374:
if (req.getParams().getBool(ShardParams.IS_SHARD,false) && !(handler instanceof SearchHandler)) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "isShard is only acceptable with search handlers");
In the Eclipse debugger, I saw that when this line was invoked, the handler
object was actually a LazyRequestHandlerWrapper
(an internal class in 3.6.2) rather than a SearchHandler
, causing the if
statement to evaluate to false. According to the LazyRequestHandlerWrapper
documentation:
/** * The <code>LazyRequestHandlerWrapper</core> wraps any {@link SolrRequestHandler}. * Rather then instanciate and initalize the handler on startup, this wrapper waits * until it is actually called. This should only be used for handlers that are * unlikely to be used in the normal lifecycle. * * You can enable lazy loading in solrconfig.xml using: * * <pre> * <requestHandler name="..." class="..." startup="lazy"> * ... * </requestHandler> * </pre> * * This is a private class - if there is a real need for it to be public, it could * move * * @version $Id: RequestHandlers.java 1306137 2012-03-28 03:30:52Z dsmiley $ * @since solr 1.2 */
Aha! So that’s why my /spell
handler was behaving strangely – it was the only one using lazy loading.
Well, I never needed the lazy loading that badly anyway. Setting lazy="false"
in the XML configuration immediately corrected the problem.
Interestingly, it appears that the offending code has been commented out in Solr 4.3.0 (SolrCore.java:1812-1814):
// TODO: this doesn't seem to be working correctly and causes problems with the example server and distrib (for example /spell) // if (req.getParams().getBool(ShardParams.IS_SHARD,false) && !(handler instanceof SearchHandler)) // throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,"isShard is only acceptable with search handlers");
I suspect that the reason this code backfires is that, in example/solr/collection1/conf/solrconfig.xml
, the /spell
handler is defined as lazy="true"
. This would mean that the commented code could be fixed by simply publicizing the LazyRequestHandlerWrapper
class and accounting for handler wrapping in the if
statement:
SolrRequestHandler trueHandler = handler instanceof LazyRequestHandlerWrapper ? ((LazyRequestHandlerWrapper)handler).getWrappedHandler() : handler; if (req.getParams().getBool(ShardParams.IS_SHARD,false) && !(trueHandler instanceof SearchHandler)) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,"isShard is only acceptable with search handlers");
I’ve already submitted this as a patch to Solr. We’ll see if the developers agree with my hunch.