From 2330feb4cba0ca9eb7c6e785c123bdc295f530a3 Mon Sep 17 00:00:00 2001 From: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:01:07 -0400 Subject: [PATCH] HPCC-32603 prevent Grid network races adds AbortController signals to the Grid's queries, so that subsequent requests will properly abort any pending request Signed-off-by: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com> --- esp/src/package-lock.json | 9 ++++----- esp/src/package.json | 2 +- esp/src/src-react/components/controls/Grid.tsx | 11 ++++++++++- esp/src/src/ESPWorkunit.ts | 4 ++-- esp/src/src/store/Deferred.ts | 2 +- esp/src/src/store/Paged.ts | 6 +++--- esp/src/src/store/Store.ts | 11 ++++++++--- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/esp/src/package-lock.json b/esp/src/package-lock.json index 3391efbb526..d38e8aecddd 100644 --- a/esp/src/package-lock.json +++ b/esp/src/package-lock.json @@ -18,7 +18,7 @@ "@hpcc-js/chart": "2.84.1", "@hpcc-js/codemirror": "2.63.0", "@hpcc-js/common": "2.72.0", - "@hpcc-js/comms": "2.95.0", + "@hpcc-js/comms": "2.96.0", "@hpcc-js/dataflow": "8.1.7", "@hpcc-js/eclwatch": "2.75.3", "@hpcc-js/graph": "2.86.0", @@ -2082,10 +2082,9 @@ } }, "node_modules/@hpcc-js/comms": { - "version": "2.95.0", - "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.95.0.tgz", - "integrity": "sha512-kzEyDxf1Msus5rhU0yO826JxgIl2kh/bI7yNTxHAlCfLKp4SU//zrj/MK5SuEDs2lUHvmTkfx1jbfGUAK3RGFg==", - "license": "Apache-2.0", + "version": "2.96.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.96.0.tgz", + "integrity": "sha512-8faDDSBvH22lN1LHhD6xtoT/FSV3YGu7hZahuRtKp4JG27ai73AFcNOJNxyqUZ9SLN7R3oNrujqp04xpyQ6nIA==", "dependencies": { "@hpcc-js/ddl-shim": "^2.21.0", "@hpcc-js/util": "^2.52.0", diff --git a/esp/src/package.json b/esp/src/package.json index de3511485ec..9c023a0d207 100644 --- a/esp/src/package.json +++ b/esp/src/package.json @@ -44,7 +44,7 @@ "@hpcc-js/chart": "2.84.1", "@hpcc-js/codemirror": "2.63.0", "@hpcc-js/common": "2.72.0", - "@hpcc-js/comms": "2.95.0", + "@hpcc-js/comms": "2.96.0", "@hpcc-js/dataflow": "8.1.7", "@hpcc-js/eclwatch": "2.75.3", "@hpcc-js/graph": "2.86.0", diff --git a/esp/src/src-react/components/controls/Grid.tsx b/esp/src/src-react/components/controls/Grid.tsx index 129271d47b3..277e1ad261b 100644 --- a/esp/src/src-react/components/controls/Grid.tsx +++ b/esp/src/src-react/components/controls/Grid.tsx @@ -237,12 +237,21 @@ const FluentStoreGrid: React.FunctionComponent = ({ }); }); + const abortController = React.useRef(); + + React.useEffect(() => { + if (abortController.current) { + abortController.current.abort({ message: "Grid aborting stale request" }); + } + abortController.current = new AbortController(); + }, [query]); + const refreshTable = useDeepCallback((clearSelection = false) => { if (isNaN(start) || isNaN(count)) return; if (clearSelection) { selectionHandler.setItems([], true); } - const storeQuery = store.query({ ...query }, { start, count, sort: sorted ? [sorted] : undefined }); + const storeQuery = store.query({ ...query }, { start, count, sort: sorted ? [sorted] : undefined }, abortController.current.signal); storeQuery.total.then(total => { setTotal(total); }); diff --git a/esp/src/src/ESPWorkunit.ts b/esp/src/src/ESPWorkunit.ts index 1efadc5afc1..5cb9b91aeb5 100644 --- a/esp/src/src/ESPWorkunit.ts +++ b/esp/src/src/ESPWorkunit.ts @@ -1082,11 +1082,11 @@ export function CreateWUQueryStore(): BaseStore { + }, "Wuid", (request, abortSignal) => { if (request.Sortby && request.Sortby === "TotalClusterTime") { request.Sortby = "ClusterTime"; } - return service.WUQuery(request).then(response => { + return service.WUQuery(request, abortSignal).then(response => { const page = { start: undefined, end: undefined diff --git a/esp/src/src/store/Deferred.ts b/esp/src/src/store/Deferred.ts index db7c3288666..a0f4d0c65ce 100644 --- a/esp/src/src/store/Deferred.ts +++ b/esp/src/src/store/Deferred.ts @@ -68,7 +68,7 @@ export class Deferred implements Thenable { } } -export class DeferredResponse extends Deferred { +export class DeferredResponse extends Deferred { // --- Legacy Dojo Support (fake QeuryResults) --- forEach(callback) { diff --git a/esp/src/src/store/Paged.ts b/esp/src/src/store/Paged.ts index eb57375376a..b4c1025e8db 100644 --- a/esp/src/src/store/Paged.ts +++ b/esp/src/src/store/Paged.ts @@ -16,7 +16,7 @@ function dataPage(array: T[], start: number, size: number, total: number): Pa return retVal; } -type FetchData = (query: QueryRequest) => Thenable<{ data: T[], total: number }>; +type FetchData = (query: QueryRequest, abortSignal?: AbortSignal) => Thenable<{ data: T[], total: number }>; export interface RequestFields { start: keyof T; @@ -43,7 +43,7 @@ export class Paged ext this._fetchData = fetchData; } - fetchData(request: QueryRequest, options: QueryOptions): ThenableResponse { + fetchData(request: QueryRequest, options: QueryOptions, abortSignal?: AbortSignal): ThenableResponse { if (options.start !== undefined && options.count !== undefined) { request[this._requestFields.start] = options.start as any; request[this._requestFields.count] = options.count as any; @@ -52,7 +52,7 @@ export class Paged ext request[this._requestFields.sortBy] = options.sort[0].attribute as any; request[this._requestFields.descending] = options.sort[0].descending as any; } - return this._fetchData(request).then(response => { + return this._fetchData(request, abortSignal).then(response => { response.data.forEach(row => { this.index[this.getIdentity(row)] = row; }); diff --git a/esp/src/src/store/Store.ts b/esp/src/src/store/Store.ts index d41d24e893c..9e8aab02b15 100644 --- a/esp/src/src/store/Store.ts +++ b/esp/src/src/store/Store.ts @@ -1,5 +1,8 @@ import * as QueryResults from "dojo/store/util/QueryResults"; import { DeferredResponse, Thenable } from "./Deferred"; +import { scopedLogger } from "@hpcc-js/util"; + +const logger = scopedLogger("src/store/Store.ts"); // Query --- export type Key = string | number | symbol; @@ -32,7 +35,7 @@ export abstract class BaseStore { this.responseIDField = responseIDField; } - protected abstract fetchData(request: QueryRequest, options: QueryOptions): ThenableResponse; + protected abstract fetchData(request: QueryRequest, options: QueryOptions, abortSignal?: AbortSignal): ThenableResponse; abstract get(id: string | number): T; @@ -40,11 +43,13 @@ export abstract class BaseStore { return object[this.responseIDField]; } - protected query(request: QueryRequest, options: QueryOptions): DeferredResponse { + protected query(request: QueryRequest, options: QueryOptions, abortSignal?: AbortSignal): DeferredResponse { const retVal = new DeferredResponse(); - this.fetchData(request, options).then((data: QueryResponse) => { + this.fetchData(request, options, abortSignal).then((data: QueryResponse) => { retVal.total.resolve(data.total); retVal.resolve(data); + }, (err) => { + logger.debug(err); }); return QueryResults(retVal); }