-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cancellation of interpreter execution #40238
Conversation
Pinging @elastic/kibana-app-arch |
💚 Build Succeeded |
@@ -128,6 +128,8 @@ export class DashboardAppController { | |||
const panelActionsRegistry = Private(ContextMenuActionsRegistryProvider); | |||
const getUnhashableStates = Private(getUnhashableStatesProvider); | |||
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); | |||
const abortController = new AbortController(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're going to want to import abortcontroller-polyfill
here as some browsers don't support this natively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I think you're going to want to create a new AbortController
for each update, not just one on the controller load.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukasolson I had thought about the polyfill question too, because I was worried that any plugin dev who wanted to use this feature would need to deal with the polyfill. But then I noticed we are importing it here: https://github.com/elastic/kibana/blob/master/src/legacy/ui/ui_bundles/app_entry_template.js#L40
I'm not super familiar with how the entry template works, but I assume this means we won't need to re-import that polyfill elsewhere in kibana?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you could totally be right. I'll give this PR a spin on IE just to make sure.
@@ -128,6 +128,8 @@ export class DashboardAppController { | |||
const panelActionsRegistry = Private(ContextMenuActionsRegistryProvider); | |||
const getUnhashableStates = Private(getUnhashableStatesProvider); | |||
const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); | |||
const abortController = new AbortController(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukasolson I had thought about the polyfill question too, because I was worried that any plugin dev who wanted to use this feature would need to deal with the polyfill. But then I noticed we are importing it here: https://github.com/elastic/kibana/blob/master/src/legacy/ui/ui_bundles/app_entry_template.js#L40
I'm not super familiar with how the entry template works, but I assume this means we won't need to re-import that polyfill elsewhere in kibana?
@@ -71,6 +71,11 @@ export function interpreterProvider(config: any) { | |||
// if something failed, just return the failure | |||
if (getType(newContext) === 'error') return newContext; | |||
|
|||
// if execution was aborted return error | |||
if (handlers.abortSignal && handlers.abortSignal.aborted) { | |||
return createError(`abort was requested`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createError
is surfaced in the UI... is this definitely the behavior we want? Like, if we add a button for someone to cancel, do we want to always see an error message, since technically this would be the expected behavior from a user perspective? (Or maybe it should be a confirmation/success message?)
Also, I'm wondering if it would be better to throw
an error here instead, and catch it below? That way all of our error handling happens in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I've changed it to a simple Error
in 0c429c1. We should probably eventually make a separate AbortedError
or something of the sort.
I added a few commits here, I don't think we need to have the dashboard itself be responsible for creating the abort signal. I think it makes more sense to have it in the handler itself, seeing as how it determines when a fetch is necessary. It also has a |
💔 Build Failed |
@@ -71,6 +71,11 @@ export function interpreterProvider(config: any) { | |||
// if something failed, just return the failure | |||
if (getType(newContext) === 'error') return newContext; | |||
|
|||
// if execution was aborted return error | |||
if (handlers.abortSignal && handlers.abortSignal.aborted) { | |||
return new Error(`abort was requested`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something with word expression in it, like
new Error('Expression was aborted.');
yeah this makes sense to me too... it feels like a simpler place to start having it centralized in one place, and also being able to rely on the existing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regarding moving to loader: then the only way to stop the request is to destroy the loader (embeddable)
which is probably not what we want. If i am looking at the dashboard, and i enter a query, then before i got response i enter another query (very easy ... i enter 20, press enter, i add another 0, to get 200 and press enter) ... now all my embeddables would get destroyed, instead of just updated.
i think we need to make difference between aborting requests and destroying. This doens't mean however that the logic for this can't live in the loader. But on the other hand, lets not forget that we'll want to cancel more than just visualization requests, and long term in most places we won't be using visualize loader anyway.
src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx
Outdated
Show resolved
Hide resolved
src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx
Outdated
Show resolved
Hide resolved
@@ -102,6 +102,11 @@ const CourierRequestHandlerProvider = function () { | |||
request.stats(getRequestInspectorStats(requestSearchSource)); | |||
|
|||
try { | |||
if (abortSignal) { | |||
abortSignal.addEventListener('abort', () => { | |||
requestSearchSource.cancelQueued(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what will this actually do ?
- cancel queued (so requests that didn't start yet ?)
- cancel current requests ?
- on search source or on courier level ? (what happens if the request was batched with another one ?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It aborts all incomplete requests for this searchSource
object. In practice, because of Courier, requests from different searchSource
objects are batched up, and cancelling one will cancel all that are bundled within the same _msearch. I don't think this is necessarily a bad thing because it will probably always be what we want. For folks that disable batching, this will only cancel the request for this specific searchSource
.
This isn't quite true... In the PR you can see that we are aborting both when the handler is destroyed as well as when a new fetch is requested. |
this.dataLoaderParams.query = params.query; | ||
} | ||
const fetchRequired = ['timeRange', 'filters', 'query'].some(key => params.hasOwnProperty(key)); | ||
this.dataLoaderParams = { ...this.dataLoaderParams, ...otherParams }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason this was changed is because I had originally added 'abortSignal'
in here, and didn't feel like copying/pasting the exact same code 4 times made sense when we could just do it like this. Then I ended up removing 'abortSignal'
from here and actually creating it in the handler. I don't mind reverting these changes if it makes people feel more comfortable :)
Totally agreed. I still don't think it makes sense to have the dashboard be responsible for creating the signal and passing it through the dashboard grid/panel/viewport, to the embeddable/handler/loader, etc. The dashboard is responsible for managing state and passing it down to the embeddable. The embeddable/handler is responsible for handling state changes, and determining whether or not a fetch is needed (and in turn the expression is interpreted), and whether or not an existing fetch should be cancelled. Other places that want to cancel the interpreter execution can do things the same way. |
💔 Build Failed |
there is still a question on how to handle user aborts.
we still need something to handle user clicking on a cancel button (at some point we will provide it). if we decide to expose yet another method, it doesn't need to be part of this PR until we actually need it, but i think we should make the decision on how to handle it now, as if we decide to pass abortsignal in instead of exposing cancel method, that will affect other methods as well (most likely). any strong opinions about having it either way ? cc @timroes @joshdover just pinging you here to see whats your opinion on the matter. |
Just wanted to also comment |
Yeah I agree with this; if we don't need it now, let's not expose it. And since we're going to go down the path of exposing a way to manually cancel eventually, then I suppose you are right it makes more sense to put it closer to where we expect it to live in the future, vs in the request handler where would have to be later removed anyway.
++ I totally agree this feels like the natural place to put this if you want to expose a method for users to initiate a cancellation -- so you have reload, destroy, cancel, update, etc. I suppose the more fundamental design question @ppisljar was getting at is: do we want I see pros and cons to each approach; what's probably more important is keeping consistency in our implementations across various services (especially core + app arch services). |
Yeah, that makes sense. I think having these methods instead of passing in an An For example, say I have an embeddable that actually creates new documents in Elasticsearch. If I submit a request to create a new document, then navigate away before the request completes, I might not actually want the request to be aborted in the |
…ff for canceling interpreter
As I was trimming down this PR to not include the stuff related to actually aborting visualize loading (which will come in a separate PR) I was thinking, Something like this: async function invokeChain(chainArr: any, context: any, abortSignal: AbortSignal): Promise<any> {
if (!chainArr.length) return Promise.resolve(context);
// if execution was aborted return error
if (abortSignal && abortSignal.aborted) {
return new Error('Expression was aborted.');
}
... I'm going to move forward with this approach unless someone suggests differently. |
💔 Build Failed |
(@lukasolson summarizing our offline conversation) Since the So cancellation works either way, whether the signal is in But the one thing we'll have to solve in either case is how we deal with this in |
💚 Build Succeeded |
@@ -71,8 +71,16 @@ export function interpreterProvider(config: any) { | |||
// if something failed, just return the failure | |||
if (getType(newContext) === 'error') return newContext; | |||
|
|||
// if execution was aborted return error | |||
if (handlers.abortSignal && handlers.abortSignal.aborted) { | |||
return createError({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this back to being createError
because of the comment below:
The interpreter should never fail. It should always return a
{type: error}
on failure
The consumers can check if err.name === 'AbortError'
and handle those failures differently than other failures.
@ppisljar @lukeelmers I've trimmed this PR down specifically to just be the interpreter portion, and will create a new PR that relies on this one to actually pass in |
💔 Build Failed |
💔 Build Failed |
Retest |
💚 Build Succeeded |
@ppisljar gave this the LGTM in a message to me, because he couldn't "approve" of his own PR 😄 |
* add AbortSignal to interpreter * adding AbortSignal to visualize_loader * adding AbortSignal to embeddables and dashboard * passing AbortSignal to courier request handler * Remove abort signal from dashboard and move to handler, and abort fetches when necessary * Remove the rest of the references to abort signal in dashboard * Revert changes to dashboard_app * Remove code related to canceling visualize requests and only keep stuff for canceling interpreter * Use createError
7.x (7.4.0): 3ccc9a9 |
…-or-edit-existing-rollup-job * 'master' of github.com:elastic/kibana: (114 commits) [ML] Fixing empty index pattern list (elastic#42299) [Markdown] Shim new platform - cleanup plugin (elastic#41760) [Code] Enable hierarchicalDocumentSymbolSupport for java language server (elastic#42233) Add New Platform mocks for data plugin (elastic#42261) Http server route handler implementation (elastic#41894) [SR] Allow custom index pattern to be used instead of selectable list when choosing indices to restore (elastic#41534) [Code] distributed Code abstraction (elastic#41374) [SIEM] Sets page titles to the current page you are on (elastic#42157) Saved Objects export API stable type order (elastic#42310) cancellation of interpreter execution (elastic#40238) [SIEM] Fixes a crash when Machine Learning influencers is an undefined value (elastic#42198) Changed the job to work with a dedicated index (elastic#42297) FTR: fix testSubjects.missingOrFail (elastic#42290) Increase retry timeout to prevent flaky tests (elastic#42291) Spaces - make space a hidden saved object type (elastic#41688) Allow applications to register feature privileges which are excluded from the base privileges (elastic#41300) Disable flaky log column reorder test (elastic#42285) Fixing add element in element reducer (elastic#42276) Fix infinite loop (elastic#42228) [Maps][File upload] Remove geojson deep clone logic, handle on maps side (elastic#41835) ...
Summary
Adds cancelation support to interpreter via AbortSignal
Dev Docs
The interpreter now accepts an
AbortController
signal insidehandlers
for aborting execution. Sincehandlers
is passed down to each function in the interpreter, functions themselves can handle additional cleanup usinghandlers.abortSignal
if it is available.Checklist
Use
strikethroughsto remove checklist items you don't feel are applicable to this PR.For maintainers