Skip to content

Commit

Permalink
fix(#2222): fix scale pool memory leak
Browse files Browse the repository at this point in the history
  • Loading branch information
hustcc authored and simaQ committed Mar 27, 2020
1 parent 7681374 commit 7f23a4c
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 10 deletions.
27 changes: 27 additions & 0 deletions src/chart/util/scale-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface ScaleMeta {
readonly key: string;
readonly scale: Scale;
scaleDef: ScaleOption;
syncKey?: string;
}

/** @ignore */
Expand Down Expand Up @@ -123,6 +124,7 @@ export class ScalePool {

// 2. 缓存到 syncScales,构造 Record<sync, string[]> 数据结构
const syncKey = this.getSyncKey(sm);
sm.syncKey = syncKey; // 设置 sync 同步的 key

// 因为存在更新 scale 机制,所以在缓存之前,先从原 syncScales 中去除 sync 的缓存引用
this.removeFromSyncScales(key);
Expand Down Expand Up @@ -155,6 +157,31 @@ export class ScalePool {
return scaleMeta && scaleMeta.scale;
}

/**
* 在 view 销毁的时候,删除 scale 实例,防止内存泄露
* @param key
*/
public deleteScale(key: string) {
let scaleMeta = this.getScaleMeta(key);
if (scaleMeta) {
const { syncKey } = scaleMeta;

const scaleKeys = this.syncScales.get(syncKey);

// 移除同步的关系
if (scaleKeys && scaleKeys.length) {
const idx = scaleKeys.indexOf(key);

if (idx !== -1) {
scaleKeys.splice(idx, 1);
}
}
}

// 删除 scale 实例
this.scales.delete(key);
}

/**
* 清空
*/
Expand Down
29 changes: 19 additions & 10 deletions src/chart/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ export class View extends Base {
private isPreMouseInPlot: boolean = false;
/** 默认标识位,用于判定数据是否更新 */
private isDataChanged: boolean = false;
/** 从当前这个 view 创建的 scale key */
private createdScaleKeys = new Map<string, boolean>();

constructor(props: ViewCfg) {
super({ visible: props.visible });
Expand Down Expand Up @@ -227,7 +229,6 @@ export class View extends Base {
public clear() {
this.emit(VIEW_LIFE_CIRCLE.BEFORE_CLEAR);
// 1. 清空缓存和计算数据
this.scalePool.clear();
this.filteredData = [];
this.coordinateInstance = undefined;
this.isDataChanged = false; // 复位
Expand All @@ -243,6 +244,12 @@ export class View extends Base {
controller.clear();
});

// 4. 删除 scale 缓存
this.createdScaleKeys.forEach((v: boolean, k: string) => {
this.getRootView().scalePool.deleteScale(k);
});
this.createdScaleKeys.clear();

// 递归处理子 view
each(this.views, (view: View) => {
view.clear();
Expand Down Expand Up @@ -1215,21 +1222,18 @@ export class View extends Base {
* @param scaleDef
* @param key
*/
protected createScale(field: string, data: Data, scaleDef: ScaleOption, key?: string): Scale {
protected createScale(field: string, data: Data, scaleDef: ScaleOption, key: string): Scale {
// 1. 合并 field 对应的 scaleDef,合并原则是底层覆盖顶层(就近原则)
const currentScaleDef = get(this.options.scales, [field]);
const mergedScaleDef = { ...currentScaleDef, ...scaleDef };

// 2. 生成默认的 key
const defaultKey = key ? key : this.getScaleKey(field);

// 3. 是否存在父 view,在则递归,否则创建
// 2. 是否存在父 view,在则递归,否则创建
if (this.parent) {
return this.parent.createScale(field, data, mergedScaleDef, defaultKey);
return this.parent.createScale(field, data, mergedScaleDef, key);
}

// 4. 在根节点 view 通过 scalePool 创建
return this.scalePool.createScale(field, data, mergedScaleDef, defaultKey);
// 3. 在根节点 view 通过 scalePool 创建
return this.scalePool.createScale(field, data, mergedScaleDef, key);
}

/**
Expand Down Expand Up @@ -1482,12 +1486,17 @@ export class View extends Base {
const scaleDef = get(scales, [field]);

// 调用方法,递归去创建
const key = this.getScaleKey(field);
this.createScale(
field,
// 分组字段的 scale 使用未过滤的数据创建
groupedFields.includes(field) ? data : filteredData,
scaleDef
scaleDef,
key,
);

// 缓存从当前 view 创建的 scale key
this.createdScaleKeys.set(key, true);
});
}

Expand Down
72 changes: 72 additions & 0 deletions tests/bugs/2222-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Chart } from '../../src';
import { createDiv } from '../util/dom';

describe('#2222', () => {
const div = createDiv();
div.style.height = '400px';

const data = [
{ year: '1991', v1: 3, v2: 6 },
{ year: '1992', v1: 4, v2: 8 },
];
const chart = new Chart({
container: div,
autoFit: true,
height: 200,
});

const v1 = chart.createView();
v1.data(data);
v1.point().position('year*v1');

const v2 = chart.createView();
v2.data(data);
v2.line().position('year*v2');

chart.scale({
v1: { sync: 'value' },
v2: { sync: 'value' },
});

chart.render();

it('scale pool should be clear when remove view', () => {
// @ts-ignore
expect(chart.scalePool.scales.size).toBe(4);
// @ts-ignore
expect(chart.scalePool.syncScales.get('value').length).toBe(2);

// 重新渲染
chart.render();
// @ts-ignore
expect(chart.scalePool.scales.size).toBe(4);
// @ts-ignore
expect(chart.scalePool.syncScales.get('value').length).toBe(2);

// 删除
chart.removeView(v1);
// @ts-ignore
expect(chart.scalePool.scales.size).toBe(2);
// @ts-ignore
expect(chart.scalePool.syncScales.get('value').length).toBe(1);

const v3 = chart.createView();
v3.data(data);
v3.point().position('year*v1');

chart.render();
// @ts-ignore
expect(chart.scalePool.scales.size).toBe(4);
// @ts-ignore
expect(chart.scalePool.syncScales.get('value').length).toBe(2);
});

it('scale pool should be clear when clear', () => {
chart.clear();

// @ts-ignore
expect(chart.scalePool.scales.size).toBe(0);
// @ts-ignore
expect(chart.scalePool.syncScales.get('value').length).toBe(0);
})
});

0 comments on commit 7f23a4c

Please sign in to comment.