Skip to content

Commit bd79f40

Browse files
committed
Merge pull request #9595 from jpountz/test/context_closed
Tests: Assert that we do not leak SearchContexts. Close #9595
2 parents 00a9db7 + a1505e7 commit bd79f40

11 files changed

+236
-6
lines changed

src/main/java/org/elasticsearch/node/Node.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public Node(Settings preparedSettings, boolean loadConfigSettings) throws Elasti
186186
}
187187
modules.add(new RiversModule(settings));
188188
modules.add(new IndicesModule(settings));
189-
modules.add(new SearchModule());
189+
modules.add(new SearchModule(settings));
190190
modules.add(new ActionModule(false));
191191
modules.add(new MonitorModule(settings));
192192
modules.add(new GatewayModule());
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.search;
21+
22+
import org.elasticsearch.common.inject.AbstractModule;
23+
24+
public class DefaultSearchServiceModule extends AbstractModule {
25+
26+
@Override
27+
protected void configure() {
28+
bind(SearchService.class).asEagerSingleton();
29+
}
30+
31+
}

src/main/java/org/elasticsearch/search/SearchModule.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
package org.elasticsearch.search;
2121

2222
import com.google.common.collect.ImmutableList;
23+
2324
import org.elasticsearch.common.inject.AbstractModule;
2425
import org.elasticsearch.common.inject.Module;
2526
import org.elasticsearch.common.inject.SpawnModules;
27+
import org.elasticsearch.common.settings.Settings;
2628
import org.elasticsearch.index.query.functionscore.FunctionScoreModule;
2729
import org.elasticsearch.index.search.morelikethis.MoreLikeThisFetchService;
2830
import org.elasticsearch.search.action.SearchServiceTransportAction;
@@ -47,16 +49,27 @@
4749
*/
4850
public class SearchModule extends AbstractModule implements SpawnModules {
4951

52+
private final Settings settings;
53+
54+
public SearchModule(Settings settings) {
55+
this.settings = settings;
56+
}
57+
5058
@Override
5159
public Iterable<? extends Module> spawnModules() {
52-
return ImmutableList.of(new TransportSearchModule(), new HighlightModule(), new SuggestModule(), new FunctionScoreModule(), new AggregationModule());
60+
return ImmutableList.of(
61+
new SearchServiceModule(settings),
62+
new TransportSearchModule(),
63+
new HighlightModule(),
64+
new SuggestModule(),
65+
new FunctionScoreModule(),
66+
new AggregationModule());
5367
}
5468

5569
@Override
5670
protected void configure() {
5771
bind(DfsPhase.class).asEagerSingleton();
5872
bind(QueryPhase.class).asEagerSingleton();
59-
bind(SearchService.class).asEagerSingleton();
6073
bind(SearchPhaseController.class).asEagerSingleton();
6174

6275
bind(FetchPhase.class).asEagerSingleton();

src/main/java/org/elasticsearch/search/SearchService.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ public void afterIndexDeleted(Index index, @IndexSettings Settings indexSettings
182182
this.indicesWarmer.addListener(new SearchWarmer());
183183
}
184184

185+
protected void putContext(SearchContext context) {
186+
final SearchContext previous = activeContexts.put(context.id(), context);
187+
assert previous == null;
188+
}
189+
190+
protected SearchContext removeContext(long id) {
191+
return activeContexts.remove(id);
192+
}
193+
185194
@Override
186195
protected void doStart() throws ElasticsearchException {
187196
}
@@ -191,7 +200,6 @@ protected void doStop() throws ElasticsearchException {
191200
for (final SearchContext context : activeContexts.values()) {
192201
freeContext(context.id());
193202
}
194-
activeContexts.clear();
195203
}
196204

197205
@Override
@@ -525,7 +533,7 @@ final SearchContext createAndPutContext(ShardSearchRequest request) throws Elast
525533
SearchContext context = createContext(request, null);
526534
boolean success = false;
527535
try {
528-
activeContexts.put(context.id(), context);
536+
putContext(context);
529537
context.indexShard().searchService().onNewContext(context);
530538
success = true;
531539
return context;
@@ -590,7 +598,7 @@ private void freeAllContextForIndex(Index index) {
590598

591599

592600
public boolean freeContext(long id) {
593-
final SearchContext context = activeContexts.remove(id);
601+
final SearchContext context = removeContext(id);
594602
if (context != null) {
595603
try {
596604
context.indexShard().searchService().onFreeContext(context);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.search;
21+
22+
import com.google.common.collect.ImmutableList;
23+
24+
import org.elasticsearch.common.inject.AbstractModule;
25+
import org.elasticsearch.common.inject.Module;
26+
import org.elasticsearch.common.inject.SpawnModules;
27+
import org.elasticsearch.common.settings.Settings;
28+
29+
import static org.elasticsearch.common.inject.Modules.createModule;
30+
31+
public class SearchServiceModule extends AbstractModule implements SpawnModules {
32+
33+
public static final String IMPL = "search.service_impl";
34+
35+
private final Settings settings;
36+
37+
public SearchServiceModule(Settings settings) {
38+
this.settings = settings;
39+
}
40+
41+
@Override
42+
protected void configure() {
43+
}
44+
45+
@Override
46+
public Iterable<? extends Module> spawnModules() {
47+
return ImmutableList.of(createModule(settings.getAsClass(IMPL, DefaultSearchServiceModule.class), settings));
48+
}
49+
}

src/test/java/org/elasticsearch/search/scroll/SearchScrollWithFailingNodesTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public void testScanScrollWithShardExceptions() throws Exception {
106106
assertThat(searchResponse.getSuccessfulShards(), equalTo(numberOfSuccessfulShards));
107107
} while (searchResponse.getHits().hits().length > 0);
108108
assertThat(numHits, greaterThan(0l));
109+
110+
clearScroll(searchResponse.getScrollId());
109111
}
110112

111113
}

src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,10 @@ public final void before() throws Exception {
18381838

18391839
@After
18401840
public final void after() throws Exception {
1841+
// Deleting indices is going to clear search contexts implicitely so we
1842+
// need to check that there are no more in-flight search contexts before
1843+
// we remove indices
1844+
super.ensureAllSearchContextsReleased();
18411845
if (runTestScopeLifecycle()) {
18421846
afterInternal(false);
18431847
}

src/test/java/org/elasticsearch/test/ElasticsearchTestCase.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.elasticsearch.test.cache.recycler.MockBigArrays;
4747
import org.elasticsearch.test.cache.recycler.MockPageCacheRecycler;
4848
import org.elasticsearch.test.junit.listeners.LoggingListener;
49+
import org.elasticsearch.test.search.MockSearchService;
4950
import org.elasticsearch.test.store.MockDirectoryHelper;
5051
import org.elasticsearch.threadpool.ThreadPool;
5152
import org.junit.*;
@@ -212,6 +213,16 @@ public void ensureAllArraysReleased() throws Exception {
212213
MockBigArrays.ensureAllArraysAreReleased();
213214
}
214215

216+
@After
217+
public void ensureAllSearchContextsReleased() throws Exception {
218+
assertBusy(new Runnable() {
219+
@Override
220+
public void run() {
221+
MockSearchService.assertNoInFLightContext();
222+
}
223+
});
224+
}
225+
215226
public static boolean hasUnclosedWrapper() {
216227
for (MockDirectoryWrapper w : MockDirectoryHelper.wrappers) {
217228
if (w.isOpen()) {

src/test/java/org/elasticsearch/test/InternalTestCluster.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.common.util.concurrent.Futures;
3030
import com.google.common.util.concurrent.ListenableFuture;
3131
import com.google.common.util.concurrent.SettableFuture;
32+
3233
import org.apache.lucene.util.AbstractRandomizedTest;
3334
import org.apache.lucene.util.IOUtils;
3435
import org.elasticsearch.ElasticsearchException;
@@ -86,10 +87,12 @@
8687
import org.elasticsearch.node.service.NodeService;
8788
import org.elasticsearch.plugins.PluginsService;
8889
import org.elasticsearch.search.SearchService;
90+
import org.elasticsearch.search.SearchServiceModule;
8991
import org.elasticsearch.test.cache.recycler.MockBigArraysModule;
9092
import org.elasticsearch.test.cache.recycler.MockPageCacheRecyclerModule;
9193
import org.elasticsearch.test.disruption.ServiceDisruptionScheme;
9294
import org.elasticsearch.test.engine.MockEngineFactory;
95+
import org.elasticsearch.test.search.MockSearchServiceModule;
9396
import org.elasticsearch.test.store.MockFSIndexStoreModule;
9497
import org.elasticsearch.test.transport.AssertingLocalTransport;
9598
import org.elasticsearch.test.transport.MockTransportService;
@@ -360,6 +363,7 @@ private static Settings getRandomNodeSettings(long seed) {
360363
builder.put(PageCacheRecyclerModule.CACHE_IMPL, MockPageCacheRecyclerModule.class.getName());
361364
builder.put(BigArraysModule.IMPL, MockBigArraysModule.class.getName());
362365
builder.put(TransportModule.TRANSPORT_SERVICE_TYPE_KEY, MockTransportService.class.getName());
366+
builder.put(SearchServiceModule.IMPL, MockSearchServiceModule.class.getName());
363367
}
364368
if (isLocalTransportConfigured()) {
365369
builder.put(TransportModule.TRANSPORT_TYPE_KEY, AssertingLocalTransport.class.getName());
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.test.search;
21+
22+
import org.elasticsearch.cache.recycler.PageCacheRecycler;
23+
import org.elasticsearch.cluster.ClusterService;
24+
import org.elasticsearch.common.inject.Inject;
25+
import org.elasticsearch.common.settings.Settings;
26+
import org.elasticsearch.common.util.BigArrays;
27+
import org.elasticsearch.indices.IndicesService;
28+
import org.elasticsearch.indices.IndicesWarmer;
29+
import org.elasticsearch.indices.cache.query.IndicesQueryCache;
30+
import org.elasticsearch.script.ScriptService;
31+
import org.elasticsearch.search.SearchService;
32+
import org.elasticsearch.search.dfs.DfsPhase;
33+
import org.elasticsearch.search.fetch.FetchPhase;
34+
import org.elasticsearch.search.internal.SearchContext;
35+
import org.elasticsearch.search.query.QueryPhase;
36+
import org.elasticsearch.threadpool.ThreadPool;
37+
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import java.util.concurrent.ConcurrentHashMap;
41+
42+
public class MockSearchService extends SearchService {
43+
44+
private static final Map<SearchContext, Throwable> ACTIVE_SEARCH_CONTEXTS = new ConcurrentHashMap<>();
45+
46+
/** Throw an {@link AssertionError} if there are still in-flight contexts. */
47+
public static void assertNoInFLightContext() {
48+
final Map<SearchContext, Throwable> copy = new HashMap<>(ACTIVE_SEARCH_CONTEXTS);
49+
if (copy.isEmpty() == false) {
50+
throw new AssertionError("There are still " + copy.size() + " in-flight contexts", copy.values().iterator().next());
51+
}
52+
}
53+
54+
@Inject
55+
public MockSearchService(Settings settings, ClusterService clusterService, IndicesService indicesService, IndicesWarmer indicesWarmer,
56+
ThreadPool threadPool, ScriptService scriptService, PageCacheRecycler pageCacheRecycler, BigArrays bigArrays,
57+
DfsPhase dfsPhase, QueryPhase queryPhase, FetchPhase fetchPhase, IndicesQueryCache indicesQueryCache) {
58+
super(settings, clusterService, indicesService, indicesWarmer, threadPool, scriptService, pageCacheRecycler, bigArrays, dfsPhase,
59+
queryPhase, fetchPhase, indicesQueryCache);
60+
}
61+
62+
@Override
63+
protected void putContext(SearchContext context) {
64+
super.putContext(context);
65+
ACTIVE_SEARCH_CONTEXTS.put(context, new RuntimeException());
66+
}
67+
68+
@Override
69+
protected SearchContext removeContext(long id) {
70+
final SearchContext removed = super.removeContext(id);
71+
if (removed != null) {
72+
ACTIVE_SEARCH_CONTEXTS.remove(removed);
73+
}
74+
return removed;
75+
}
76+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.test.search;
21+
22+
import org.elasticsearch.common.inject.AbstractModule;
23+
import org.elasticsearch.search.SearchService;
24+
25+
public class MockSearchServiceModule extends AbstractModule {
26+
27+
@Override
28+
protected void configure() {
29+
bind(SearchService.class).to(MockSearchService.class).asEagerSingleton();
30+
}
31+
32+
}

0 commit comments

Comments
 (0)