Skip to content

Commit 7cf91e1

Browse files
[7.6] Abort cancelled search requests to Elasticsearch (#56788) (#58916)
* Abort cancelled search requests to Elasticsearch (#56788) * Update abort controller library * Bootstrap * Abort when the request is aborted * Add utility and update value suggestions route * Remove bad merge * Revert switching abort controller libraries * Revert package.json in lib * Move to previous abort controller * Fix test to use fake timers to run debounced handlers * Fix loading bar not going away when cancelling * Add test for loading count * Fix test * Fix failing test Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> * Remove unnecessary tests Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent b14a700 commit 7cf91e1

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
lines changed

src/plugins/data/server/autocomplete/value_suggestions_route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { IRouter, SharedGlobalConfig } from 'kibana/server';
2424
import { Observable } from 'rxjs';
2525
import { first } from 'rxjs/operators';
2626
import { IFieldType, indexPatterns, esFilters } from '../index';
27+
import { getRequestAbortedSignal } from '../lib';
2728

2829
export function registerValueSuggestionsRoute(
2930
router: IRouter,
@@ -54,6 +55,7 @@ export function registerValueSuggestionsRoute(
5455
const { field: fieldName, query, boolFilter } = request.body;
5556
const { index } = request.params;
5657
const { dataClient } = context.core.elasticsearch;
58+
const signal = getRequestAbortedSignal(request.events.aborted$);
5759

5860
const autocompleteSearchOptions = {
5961
timeout: `${config.kibana.autocompleteTimeout.asMilliseconds()}ms`,
@@ -69,7 +71,7 @@ export function registerValueSuggestionsRoute(
6971
const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter);
7072

7173
try {
72-
const result = await dataClient.callAsCurrentUser('search', { index, body });
74+
const result = await dataClient.callAsCurrentUser('search', { index, body }, { signal });
7375

7476
const buckets: any[] =
7577
get(result, 'aggregations.suggestions.buckets') ||
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. 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 B.V. 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+
import { Subject } from 'rxjs';
21+
import { getRequestAbortedSignal } from './get_request_aborted_signal';
22+
23+
describe('abortableRequestHandler', () => {
24+
jest.useFakeTimers();
25+
26+
it('should call abort if disconnected', () => {
27+
const abortedSubject = new Subject<void>();
28+
const aborted$ = abortedSubject.asObservable();
29+
const onAborted = jest.fn();
30+
31+
const signal = getRequestAbortedSignal(aborted$);
32+
signal.addEventListener('abort', onAborted);
33+
34+
// Shouldn't be aborted or call onAborted prior to disconnecting
35+
expect(signal.aborted).toBe(false);
36+
expect(onAborted).not.toBeCalled();
37+
38+
abortedSubject.next();
39+
jest.runAllTimers();
40+
41+
// Should be aborted and call onAborted after disconnecting
42+
expect(signal.aborted).toBe(true);
43+
expect(onAborted).toBeCalled();
44+
});
45+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. 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 B.V. 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+
import { Observable } from 'rxjs';
21+
// @ts-ignore not typed
22+
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill';
23+
24+
/**
25+
* A simple utility function that returns an `AbortSignal` corresponding to an `AbortController`
26+
* which aborts when the given request is aborted.
27+
* @param aborted$ The observable of abort events (usually `request.events.aborted$`)
28+
*/
29+
export function getRequestAbortedSignal(aborted$: Observable<void>): AbortSignal {
30+
const controller = new AbortController();
31+
aborted$.subscribe(() => controller.abort());
32+
return controller.signal;
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. 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 B.V. 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+
export { getRequestAbortedSignal } from './get_request_aborted_signal';

src/plugins/data/server/search/routes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import { schema } from '@kbn/config-schema';
2121
import { IRouter } from '../../../../core/server';
22+
import { getRequestAbortedSignal } from '../lib';
2223

2324
export function registerSearchRoute(router: IRouter): void {
2425
router.post(
@@ -35,8 +36,10 @@ export function registerSearchRoute(router: IRouter): void {
3536
async (context, request, res) => {
3637
const searchRequest = request.body;
3738
const strategy = request.params.strategy;
39+
const signal = getRequestAbortedSignal(request.events.aborted$);
40+
3841
try {
39-
const response = await context.search!.search(searchRequest, {}, strategy);
42+
const response = await context.search!.search(searchRequest, { signal }, strategy);
4043
return res.ok({ body: response });
4144
} catch (err) {
4245
return res.internalError({ body: err });

0 commit comments

Comments
 (0)