Skip to content

Commit

Permalink
work on task abort issues
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Dec 25, 2018
1 parent 2c58f8b commit 90eb261
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 32 deletions.
32 changes: 23 additions & 9 deletions src/internal/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export interface ITask<T> {
* the resulting promise
*/
result: PromiseLike<T | symbol>;

/**
* aborts this task
*/
abort: () => void;
}

/**
Expand All @@ -61,12 +66,13 @@ export function oneShotIterator<T>(calc: () => T): Iterator<T> {
}


function thenFactory<T>(wrappee: PromiseLike<T>, abort: () => void) {
function thenFactory<T>(wrappee: PromiseLike<T>, abort: () => void, isAborted: () => boolean) {
function then<TResult1 = T | symbol, TResult2 = never>(onfulfilled?: ((value: T | symbol) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): IAbortAblePromiseBase<TResult1 | TResult2> {
const r = wrappee.then(onfulfilled, onrejected);
return {
then: <any>thenFactory(r, abort),
abort
then: <any>thenFactory(r, abort, isAborted),
abort,
isAborted
};
}
return then;
Expand Down Expand Up @@ -125,7 +131,10 @@ export default class TaskScheduler {
*/
pushMulti<T>(id: string, it: Iterator<T | PromiseLike<T> | null>): IAbortAblePromise<T> {
// abort task with the same id
let abortedCalled = false;
const isAborted = () => abortedCalled;
const abort = () => {
abortedCalled = true;
const index = this.tasks.findIndex((d) => d.id === id);
if (index < 0) {
return; // too late or none
Expand All @@ -137,6 +146,7 @@ export default class TaskScheduler {
};

abort(); // abort existing with same id
abortedCalled = false; // reset

let resolve: (value: T | symbol) => void;

Expand All @@ -149,14 +159,16 @@ export default class TaskScheduler {
id,
it,
result: p,
abort,
resolve: resolve!
});

this.reSchedule();

return {
then: thenFactory(p, abort),
abort
then: thenFactory(p, abort, isAborted),
abort,
isAborted
};
}

Expand All @@ -179,9 +191,7 @@ export default class TaskScheduler {
return false; // too late or none
}
const task = this.tasks[index];
this.tasks.splice(index, 1);

task.resolve(ABORTED);
task.abort();
return true;
}

Expand All @@ -193,6 +203,7 @@ export default class TaskScheduler {
this.tasks = this.tasks.filter((d) => !filter(d));
for (const task of abort) {
task.resolve(ABORTED);
task.abort();
}
}

Expand All @@ -208,6 +219,9 @@ export default class TaskScheduler {
}
this.taskId = -1;

this.tasks.splice(0, this.tasks.length).forEach((d) => d.resolve(ABORTED));
this.tasks.splice(0, this.tasks.length).forEach((d) => {
d.resolve(ABORTED);
d.abort();
});
}
}
51 changes: 33 additions & 18 deletions src/provider/ScheduledTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export class ScheduleRenderTasks extends ARenderTasks implements IRenderTaskExec
private cached<T>(key: string, it: Iterator<T | null> | (() => Promise<T>)): IRenderTask<T> {
const dontCache = this.data.length === 0;

if (this.cache.has(key) && !dontCache) {
if (this.isValidCacheEntry(key) && !dontCache) {
return this.cache.get(key)!;
}

Expand All @@ -370,22 +370,27 @@ export class ScheduleRenderTasks extends ARenderTasks implements IRenderTaskExec
this.cache.set(key, s);
}
task.then((r) => {
if (typeof r === 'symbol') {
if (this.cache.get(key) !== s) {
return;
}
if (this.cache.get(key) === s) {
// still same value replace with faster version
if (typeof r === 'symbol') {
this.cache.delete(key);
} else {
this.cache.set(key, taskNow(r));
}
});
return s;
}

private chain<T, U>(key: string, task: IRenderTask<T>, creator: (data: T) => Iterator<U | null> | (() => Promise<U>)): IRenderTask<U> {
if (this.cache.has(key)) {
if (this.isValidCacheEntry(key)) {
return this.cache.get(key)!;
}
if (task instanceof TaskNow) {
if (typeof task.v === 'symbol') {
// aborted
return taskNow(ABORTED);
}
return this.cached(key, creator(task.v));
}

Expand All @@ -404,22 +409,36 @@ export class ScheduleRenderTasks extends ARenderTasks implements IRenderTaskExec
const s = taskLater(subTask);
this.cache.set(key, s);
subTask.then((r) => {
if (typeof r === 'symbol') {
if (this.cache.get(key) !== s) {
return;
}
if (this.cache.get(key) === s) {
// still same value replace with faster version
if (typeof r === 'symbol') {
this.cache.delete(key);
} else {
this.cache.set(key, taskNow(r));
}
});
return s;
}

private isValidCacheEntry(key: string) {
if (!this.cache.has(key)) {
return false;
}
const v = this.cache.get(key);
// not an aborted task
return !((v instanceof TaskNow) && typeof v.v === 'symbol') && !(v instanceof TaskLater && v.v.isAborted());
}

private chainCopy<T, U>(key: string, task: IRenderTask<T>, creator: (data: T) => U): IRenderTask<U> {
if (this.cache.has(key)) {
if (this.isValidCacheEntry(key)) {
return this.cache.get(key)!;
}
if (task instanceof TaskNow) {
if (typeof task.v === 'symbol') {
// aborted
return taskNow(ABORTED);
}
const subTask = taskNow(creator(task.v));
this.cache.set(key, subTask);
return subTask;
Expand All @@ -435,24 +454,20 @@ export class ScheduleRenderTasks extends ARenderTasks implements IRenderTaskExec
const s = taskLater(subTask);
this.cache.set(key, s);
subTask.then((r) => {
if (typeof r === 'symbol') {
if (this.cache.get(key) !== s) {
return;
}
if (this.cache.get(key) === s) {
// still same value replace with faster version
if (typeof r === 'symbol') {
this.cache.delete(key);
} else {
this.cache.set(key, taskNow(r));
}
});
return s;
}

dataBoxPlotStats(col: Column & INumberColumn, raw?: boolean) {
const key = `${col.id}:c:data${raw ? ':braw' : ':b'}`;
if (!raw && this.valueCacheData.has(col.id) && this.data.length > 0) {
// use webworker
return this.cached(key, () => this.workers.pushStats('boxplotStats', {}, col.id, <Float32Array>this.valueCacheData.get(col.id)!));
}
return this.cached(key, this.boxplotBuilder<IAdvancedBoxPlotData>(null, col, raw));
return this.cached(`${col.id}:c:data${raw ? ':braw' : ':b'}`, this.boxplotBuilder<IAdvancedBoxPlotData>(null, col, raw));
}

dataNumberStats(col: Column & INumberColumn, raw?: boolean) {
Expand Down
13 changes: 12 additions & 1 deletion src/ui/EngineRanking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ declare function widthChanged(): void;
* @event
*/
declare function updateData(): void;
/**
* emitted when the table has be recreated
* @asMemberOf EngineRanking
* @event
*/
declare function recreate(): void;
/**
* emitted when the highlight changes
* @asMemberOf EngineRanking
Expand All @@ -48,14 +54,15 @@ declare function highlightChanged(dataIndex: number): void;
class RankingEvents extends AEventDispatcher {
static readonly EVENT_WIDTH_CHANGED = 'widthChanged';
static readonly EVENT_UPDATE_DATA = 'updateData';
static readonly EVENT_RECREATE = 'recreate';
static readonly EVENT_HIGHLIGHT_CHANGED = 'highlightChanged';

fire(type: string | string[], ...args: any[]) {
super.fire(type, ...args);
}

protected createEventList() {
return super.createEventList().concat([RankingEvents.EVENT_WIDTH_CHANGED, RankingEvents.EVENT_UPDATE_DATA, RankingEvents.EVENT_HIGHLIGHT_CHANGED]);
return super.createEventList().concat([RankingEvents.EVENT_WIDTH_CHANGED, RankingEvents.EVENT_UPDATE_DATA, RankingEvents.EVENT_RECREATE, RankingEvents.EVENT_HIGHLIGHT_CHANGED]);
}
}

Expand All @@ -66,6 +73,7 @@ const PASSIVE: AddEventListenerOptions = {
export default class EngineRanking extends ACellTableSection<RenderColumn> implements ITableSection, IEventHandler {
static readonly EVENT_WIDTH_CHANGED = RankingEvents.EVENT_WIDTH_CHANGED;
static readonly EVENT_UPDATE_DATA = RankingEvents.EVENT_UPDATE_DATA;
static readonly EVENT_RECREATE = RankingEvents.EVENT_RECREATE;
static readonly EVENT_HIGHLIGHT_CHANGED = RankingEvents.EVENT_HIGHLIGHT_CHANGED;

private _context: ICellRenderContext<RenderColumn>;
Expand Down Expand Up @@ -246,6 +254,7 @@ export default class EngineRanking extends ACellTableSection<RenderColumn> imple

on(type: typeof EngineRanking.EVENT_WIDTH_CHANGED, listener: typeof widthChanged | null): this;
on(type: typeof EngineRanking.EVENT_UPDATE_DATA, listener: typeof updateData | null): this;
on(type: typeof EngineRanking.EVENT_RECREATE, listener: typeof recreate | null): this;
on(type: typeof EngineRanking.EVENT_HIGHLIGHT_CHANGED, listener: typeof highlightChanged | null): this;
on(type: string | string[], listener: IEventListener | null): this; // required for correct typings in *.d.ts
on(type: string | string[], listener: IEventListener | null): this {
Expand Down Expand Up @@ -519,6 +528,7 @@ export default class EngineRanking extends ACellTableSection<RenderColumn> imple
column: nonUniformContext(this.columns.map((w) => w.width), 100, COLUMN_PADDING)
});

this.events.fire(EngineRanking.EVENT_RECREATE);
super.recreate();
this.events.fire(EngineRanking.EVENT_WIDTH_CHANGED);
}
Expand Down Expand Up @@ -880,6 +890,7 @@ export default class EngineRanking extends ACellTableSection<RenderColumn> imple
if (!this.bodyScroller) { // somehow not part of dom
return;
}
this.events.fire(EngineRanking.EVENT_RECREATE);
return super.recreate(this.roptions.animation ? lineupAnimation(previous, previousData, this.data) : undefined);
}

Expand Down
13 changes: 9 additions & 4 deletions src/ui/EngineRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,7 @@ export default class EngineRenderer extends AEventDispatcher {
r.updateHeaders();
}
}

for (const u of this.updateAbles) {
u(this.ctx);
}
this.updateUpdateAbles();
}

private addRanking(ranking: Ranking) {
Expand All @@ -262,6 +259,7 @@ export default class EngineRenderer extends AEventDispatcher {
this.table.widthChanged();
});
r.on(EngineRanking.EVENT_UPDATE_DATA, () => this.update([r]));
r.on(EngineRanking.EVENT_RECREATE, () => this.updateUpdateAbles());
this.forward(r, EngineRanking.EVENT_HIGHLIGHT_CHANGED);
if (this.enabledHighlightListening) {
r.enableHighlightListening(true);
Expand Down Expand Up @@ -352,10 +350,17 @@ export default class EngineRenderer extends AEventDispatcher {

this.updateSlopeGraphs(rankings);

this.updateUpdateAbles();
this.updateRotatedHeaderState();
this.table.widthChanged();
}

private updateUpdateAbles() {
for (const u of this.updateAbles) {
u(this.ctx);
}
}

private updateSlopeGraphs(rankings: EngineRanking[] = this.rankings) {
const indices = new Set(rankings.map((d) => this.rankings.indexOf(d)));

Expand Down

0 comments on commit 90eb261

Please sign in to comment.