SOLR-12155: awake threads awaiting UIF.<init> despite of exception.

This commit is contained in:
Mikhail Khludnev 2018-04-10 21:20:20 +03:00
parent 764dcc336b
commit a39a6ce672
4 changed files with 178 additions and 16 deletions

View File

@ -126,6 +126,8 @@ Bug Fixes
* SOLR-12207: Just rethrowing AssertionError caused by jdk bug in reflection with invocation details.
(ab, Dawid Weiss, Mikhail Khludnev)
* SOLR-12155: Exception from UnInvertedField constructor puts threads to infinite wait. (Mikhail Khludnev)
Optimizations
----------------------

View File

@ -35,6 +35,7 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CharsRefBuilder;
import org.apache.lucene.util.FixedBitSet;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.index.SlowCompositeReaderWrapper;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.TrieField;
@ -44,6 +45,7 @@ import org.apache.solr.search.DocSet;
import org.apache.solr.search.SolrCache;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.uninverting.DocTermOrds;
import org.apache.solr.util.TestInjection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -106,7 +108,7 @@ public class UnInvertedField extends DocTermOrds {
private SolrIndexSearcher.DocsEnumState deState;
private final SolrIndexSearcher searcher;
private static UnInvertedField uifPlaceholder = new UnInvertedField();
private static final UnInvertedField uifPlaceholder = new UnInvertedField();
private UnInvertedField() { // Dummy for synchronization.
super("fake", 0, 0); // cheapest initialization I can find.
@ -186,6 +188,8 @@ public class UnInvertedField extends DocTermOrds {
searcher.maxDoc()/20 + 2,
DEFAULT_INDEX_INTERVAL_BITS);
assert TestInjection.injectUIFOutOfMemoryError();
final String prefix = TrieField.getMainValuePrefix(searcher.getSchema().getFieldType(field));
this.searcher = searcher;
try {
@ -556,16 +560,17 @@ public class UnInvertedField extends DocTermOrds {
//////////////////////////// caching /////////////////////////////
//////////////////////////////////////////////////////////////////
@SuppressWarnings("unchecked")
public static UnInvertedField getUnInvertedField(String field, SolrIndexSearcher searcher) throws IOException {
SolrCache<String,UnInvertedField> cache = searcher.getFieldValueCache();
SolrCache cache = searcher.getFieldValueCache();
if (cache == null) {
return new UnInvertedField(field, searcher);
}
UnInvertedField uif = null;
Boolean doWait = false;
synchronized (cache) {
uif = cache.get(field);
if (uif == null) {
final Object val = cache.get(field);
if (val == null || (val instanceof Throwable)) {
/**
* We use this place holder object to pull the UninvertedField construction out of the sync
* so that if many fields are accessed in a short time, the UninvertedField can be
@ -573,8 +578,8 @@ public class UnInvertedField extends DocTermOrds {
*/
cache.put(field, uifPlaceholder);
} else {
if (uif != uifPlaceholder) {
return uif;
if (val != uifPlaceholder) {
return (UnInvertedField) val;
}
doWait = true; // Someone else has put the place holder in, wait for that to complete.
}
@ -582,35 +587,53 @@ public class UnInvertedField extends DocTermOrds {
while (doWait) {
try {
synchronized (cache) {
uif = cache.get(field); // Should at least return the placeholder, NPE if not is OK.
if (uif != uifPlaceholder) { // OK, another thread put this in the cache we should be good.
return uif;
final Object val = cache.get(field);
if (val != uifPlaceholder) { // OK, another thread put this in the cache we should be good.
if (val instanceof Throwable) {
rethrowAsSolrException(field, (Throwable) val);
} else {
return (UnInvertedField) val;
}
}
cache.wait();
}
} catch (InterruptedException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Thread interrupted in getUninvertedField.");
rethrowAsSolrException(field, e);
}
}
uif = new UnInvertedField(field, searcher);
UnInvertedField uif = null;
try {
uif = new UnInvertedField(field, searcher);
}catch(Throwable e) {
synchronized (cache) {
cache.put(field, e); // signaling the failure
cache.notifyAll();
}
rethrowAsSolrException(field, e);
}
synchronized (cache) {
cache.put(field, uif); // Note, this cleverly replaces the placeholder.
cache.notifyAll();
}
return uif;
}
protected static void rethrowAsSolrException(String field, Throwable e) {
throw new SolrException(ErrorCode.SERVER_ERROR,
"Exception occurs during uninverting "+field, e);
}
// Returns null if not already populated
@SuppressWarnings({"rawtypes", "unchecked"})
public static UnInvertedField checkUnInvertedField(String field, SolrIndexSearcher searcher) throws IOException {
SolrCache<String, UnInvertedField> cache = searcher.getFieldValueCache();
SolrCache cache = searcher.getFieldValueCache();
if (cache == null) {
return null;
}
UnInvertedField uif = cache.get(field); // cache is already synchronized, so no extra sync needed
Object uif = cache.get(field); // cache is already synchronized, so no extra sync needed
// placeholder is an implementation detail, keep it hidden and return null if that is what we got
return uif==uifPlaceholder ? null : uif;
return uif==uifPlaceholder || !(uif instanceof UnInvertedField)? null : (UnInvertedField) uif;
}
}

View File

@ -146,6 +146,8 @@ public class TestInjection {
public static Integer delayBeforeSlaveCommitRefresh=null;
public static boolean uifOutOfMemoryError = false;
public static void reset() {
nonGracefullClose = null;
failReplicaRequests = null;
@ -161,6 +163,7 @@ public class TestInjection {
failIndexFingerprintRequests = null;
wrongIndexFingerprint = null;
delayBeforeSlaveCommitRefresh = null;
uifOutOfMemoryError = false;
for (Timer timer : timers) {
timer.cancel();
@ -470,4 +473,11 @@ public class TestInjection {
return true;
}
public static boolean injectUIFOutOfMemoryError() {
if (uifOutOfMemoryError ) {
throw new OutOfMemoryError("Test Injection");
}
return true;
}
}

View File

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.request;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.lucene.index.Term;
import org.apache.lucene.util.NamedThreadFactory;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.ExecutorUtil.MDCAwareThreadPoolExecutor;
import org.apache.solr.search.facet.UnInvertedField;
import org.apache.solr.util.TestInjection;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestUnInvertedFieldException extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig.xml","schema11.xml");
}
private int numTerms;
@Override
public void setUp() throws Exception {
super.setUp();
numTerms = TestUtil.nextInt(random(), 10, 50);
createIndex(numTerms);
}
@After
@Override
public void tearDown() throws Exception {
super.tearDown();
}
String t(int tnum) {
return String.format(Locale.ROOT, "%08d", tnum);
}
void createIndex(int nTerms) {
assertU(delQ("*:*"));
for (int i=0; i<nTerms; i++) {
assertU(adoc("id", Integer.toString(i), proto.field(), t(i) ));
}
assertU(commit());
}
Term proto = new Term("field_s","");
@Test
public void testConcurrentInit() throws Exception {
final SolrQueryRequest req = req("*:*");
List<Callable<UnInvertedField>> initCallables = new ArrayList<>();
for (int i=0;i< TestUtil.nextInt(random(), 10, 30);i++) {
initCallables.add(()-> UnInvertedField.getUnInvertedField(proto.field(), req.getSearcher()));
}
final ThreadPoolExecutor pool = new MDCAwareThreadPoolExecutor(3,
TestUtil.nextInt(random(), 3, 6), 10, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory(getClass().getSimpleName()));
try {
TestInjection.uifOutOfMemoryError = true;
if (assertsAreEnabled) { // if they aren't, we check that injection is disabled in live
List<Future<UnInvertedField>> futures = initCallables.stream().map((c) -> pool.submit(c))
.collect(Collectors.toList());
for (Future<UnInvertedField> uifuture : futures) {
try {
final UnInvertedField uif = uifuture.get();
} catch (ExecutionException injection) {
SolrException solrException = (SolrException) injection.getCause();
assertEquals(ErrorCode.SERVER_ERROR.code, solrException.code());
assertSame(solrException.getCause().getClass(), OutOfMemoryError.class);
}
assertNull(UnInvertedField.checkUnInvertedField(proto.field(), req.getSearcher()));
}
TestInjection.uifOutOfMemoryError = false;
}
UnInvertedField prev = null;
List<Future<UnInvertedField>> futures = initCallables.stream().map((c) -> pool.submit(c))
.collect(Collectors.toList());
for (Future<UnInvertedField> uifuture : futures) {
final UnInvertedField uif = uifuture.get();
assertNotNull(uif);
assertSame(uif, UnInvertedField.checkUnInvertedField(proto.field(), req.getSearcher()));
if (prev != null) {
assertSame(prev, uif);
}
assertEquals(numTerms, uif.numTerms());
prev = uif;
}
} finally {
pool.shutdownNow();
req.close();
}
}
}