fix: fix issue that cause memory leak (#5587)

* fix: fix issue that may cause memories lack

* test: adjust test case to destroy graph
This commit is contained in:
Aaron 2024-03-29 11:45:18 +08:00 committed by GitHub
parent 72c5e74611
commit bcd080432f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 143 additions and 40 deletions

View File

@ -9,6 +9,10 @@ describe('behavior combo expand collapse', () => {
graph = await createDemoGraph(comboExpandCollapse, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -11,6 +11,10 @@ describe('behavior drag canvas', () => {
graph = await createDemoGraph(behaviorDragCanvas, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', () => {
expect(graph.getBehaviors()).toEqual([
'drag-canvas',
@ -78,8 +82,4 @@ describe('behavior drag canvas', () => {
await expect(graph).toMatchSnapshot(__filename);
});
it('destroy', () => {
graph.destroy();
});
});

View File

@ -9,6 +9,10 @@ describe('behavior drag combo', () => {
graph = await createDemoGraph(comboExpandCollapse, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
graph.setBehaviors([{ type: 'drag-element', dropEffect: 'link' }]);
graph.expand('combo-1');

View File

@ -10,6 +10,10 @@ describe('behavior drag element', () => {
graph = await createDemoGraph(behaviorDragNode, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);

View File

@ -11,6 +11,10 @@ describe('behavior zoom canvas', () => {
graph = await createDemoGraph(behaviorZoomCanvas, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', () => {
expect(graph.getZoom()).toBe(1);
expect(graph.getBehaviors()).toEqual([{ type: 'zoom-canvas' }]);
@ -191,8 +195,4 @@ describe('behavior zoom canvas', () => {
container?.dispatchEvent(new Event(ContainerEvent.KEY_DOWN));
expect(keydownListener).toHaveBeenCalledTimes(1);
});
it('destroy', () => {
graph.destroy();
});
});

View File

@ -9,6 +9,10 @@ describe('element change type', () => {
graph = await createDemoGraph(elementChangeType, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -9,6 +9,10 @@ describe('combo', () => {
graph = await createDemoGraph(combo, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -26,6 +26,10 @@ describe('edge polyline', () => {
graph = await createDemoGraph(edgePolyline, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('Control Points', async () => {
updateEdgeStyle(graph, 'edge-1', 'controlPoints', [[300, 190]]);

View File

@ -25,6 +25,10 @@ describe('element port', () => {
graph = await createDemoGraph(elementPort, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename, 'port_hidden');
});

View File

@ -9,6 +9,10 @@ describe('element position combo', () => {
graph = await createDemoGraph(elementPositionCombo, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -9,6 +9,10 @@ describe('element position', () => {
graph = await createDemoGraph(elementPosition, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -9,6 +9,10 @@ describe('element state', () => {
graph = await createDemoGraph(elementState, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -9,6 +9,10 @@ describe('element visibility', () => {
graph = await createDemoGraph(elementVisibility, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -9,6 +9,10 @@ describe('element zIndex', () => {
graph = await createDemoGraph(elementZIndex, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('default status', async () => {
await expect(graph).toMatchSnapshot(__filename);
});

View File

@ -14,6 +14,10 @@ describe('plugin grid line', () => {
.getElementsByClassName('g6-grid-line')! as HTMLCollectionOf<HTMLElement>;
});
afterAll(() => {
graph.destroy();
});
it('default status', () => {
expect(graph.getPlugins()).toEqual([{ type: 'grid-line', follow: false }]);
expect(gridLineElement.length).toBe(1);

View File

@ -8,10 +8,15 @@ import { omit } from '@antv/util';
describe('ElementController', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(graphElement);
});
afterAll(() => {
graph.destroy();
});
it('static', async () => {
await expect(graph).toMatchSnapshot(__filename);

View File

@ -316,7 +316,7 @@ describe('Graph', () => {
it('destroy', () => {
graph.destroy();
// @ts-expect-error context is private.
expect(graph.context).toBeUndefined();
expect(graph.context).toEqual({});
expect(graph.destroyed).toBe(true);
});
});

View File

@ -6,10 +6,15 @@ import { AABB } from '@antv/g';
describe('ViewportController', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(controllerViewport);
});
afterAll(() => {
graph.destroy();
});
it('viewport center', () => {
expect(graph.getViewportCenter()).toBeCloseTo([250, 250, 0]);
});
@ -104,18 +109,19 @@ describe('ViewportController', () => {
// @ts-expect-error
expect(graph.context.viewport.getBBoxInViewport(bbox).halfExtents).toBeCloseTo([100, 100, 0]);
});
afterAll(() => {
graph.destroy();
});
});
describe('Viewport Fit without Animation', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(viewportFit);
});
afterAll(() => {
graph.destroy();
});
it('default', async () => {
await expect(graph).toMatchSnapshot(__filename, 'before-fit');
});
@ -144,18 +150,19 @@ describe('Viewport Fit without Animation', () => {
await graph.fitCenter();
await expect(graph).toMatchSnapshot(__filename, 're-fitCenter');
});
afterAll(() => {
graph.destroy();
});
});
describe('Viewport Fit with Animation', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(viewportFit, { animation: true });
});
afterAll(() => {
graph.destroy();
});
it('default', async () => {
await expect(graph).toMatchSnapshot(__filename, 'before-fit-animation');
});
@ -184,10 +191,6 @@ describe('Viewport Fit with Animation', () => {
await graph.fitCenter();
await expect(graph).toMatchSnapshot(__filename, 're-fitCenter-animation');
});
afterAll(() => {
graph.destroy();
});
});
describe('Viewport Fit with AutoFit and Padding without Animation', () => {
@ -199,6 +202,10 @@ describe('Viewport Fit with AutoFit and Padding without Animation', () => {
});
});
afterAll(() => {
graph.destroy();
});
it('default', async () => {
await expect(graph).toMatchSnapshot(__filename, 'auto-fit-with-padding');
});
@ -214,6 +221,10 @@ describe('Viewport Fit with AutoFit and Padding with Animation', () => {
});
});
afterAll(() => {
graph.destroy();
});
it('default', async () => {
await expect(graph).toMatchSnapshot(__filename, 'auto-fit-with-padding-animation');
});

View File

@ -9,6 +9,10 @@ describe('spec theme', () => {
graph = await createDemoGraph(theme, { animation: false });
});
afterAll(() => {
graph.destroy();
});
it('theme', async () => {
const theme: ThemeOptions = 'light';

View File

@ -43,6 +43,7 @@
"dev": "vite",
"fix": "eslint ./src ./__tests__ --fix && prettier ./src __tests__ --write ",
"jest": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict --experimental-vm-modules ../../node_modules/jest/bin/jest --coverage --logHeapUsage --detectOpenHandles",
"jest:inspect": "node --inspect --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict --experimental-vm-modules ../../node_modules/jest/bin/jest --coverage --logHeapUsage --detectOpenHandles --runInBand",
"lint": "eslint ./src __tests__ --quiet && prettier ./src __tests__ --check",
"prepublishOnly": "npm run ci",
"size": "limit-size",

View File

@ -148,4 +148,10 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
const { visibility } = this.attributes;
setVisibility(this, visibility);
}
public destroy(): void {
this.shapeMap = {};
this.animateMap = {};
super.destroy();
}
}

View File

@ -66,7 +66,7 @@ export class Tooltip extends BasePlugin<TooltipOptions> {
}
public update(options: Partial<TooltipOptions>) {
this.unbundEvents();
this.unbindEvents();
super.update(options);
if (this.tooltipElement) {
this.container?.removeChild(this.tooltipElement.HTMLTooltipElement);
@ -83,7 +83,7 @@ export class Tooltip extends BasePlugin<TooltipOptions> {
this.tooltipElement = this.initTooltip();
}
private unbundEvents() {
private unbindEvents() {
const { graph } = this.context;
/** The previous event binding needs to be removed when updating the trigger. */
const events = this.getEvents();
@ -259,7 +259,7 @@ export class Tooltip extends BasePlugin<TooltipOptions> {
};
public destroy(): void {
this.unbundEvents();
this.unbindEvents();
if (this.tooltipElement) {
this.container?.removeChild(this.tooltipElement.HTMLTooltipElement);
}

View File

@ -72,11 +72,9 @@ export abstract class ExtensionController<Extension extends BaseExtension<Loosel
public destroy() {
Object.values(this.extensionMap).forEach((extension) => extension.destroy());
// @ts-expect-error force delete
delete this.context;
// @ts-expect-error force delete
delete this.extensions;
// @ts-expect-error force delete
delete this.extensionMap;
this.context = {};
this.extensions = [];
this.extensionMap = {};
}
}
@ -105,9 +103,9 @@ export class BaseExtension<T extends LooselyExtensionOption> {
public destroy() {
// @ts-expect-error force delete
delete this.context;
this.context = {};
// @ts-expect-error force delete
delete this.options;
this.options = {};
this.destroyed = true;
}

View File

@ -27,7 +27,7 @@ export class BehaviorController extends ExtensionController<BaseBehavior<CustomB
const container = this.context.canvas.getContainer();
if (container) {
[ContainerEvent.KEY_DOWN, ContainerEvent.KEY_UP].forEach((name) => {
container.addEventListener(name, this.forwardContainerEvents.bind(this));
container.addEventListener(name, this.forwardContainerEvents);
});
}
@ -105,7 +105,18 @@ export class BehaviorController extends ExtensionController<BaseBehavior<CustomB
}
};
private forwardContainerEvents(event: FocusEvent | KeyboardEvent) {
private forwardContainerEvents = (event: FocusEvent | KeyboardEvent) => {
this.context.graph.emit(event.type, event);
};
public destroy(): void {
const container = this.context.canvas.getContainer();
if (container) {
[ContainerEvent.KEY_DOWN, ContainerEvent.KEY_UP].forEach((name) => {
container.removeEventListener(name, this.forwardContainerEvents);
});
}
this.context.canvas.document.removeAllEventListeners();
super.destroy();
}
}

View File

@ -253,7 +253,10 @@ export class Canvas {
}
public destroy() {
Object.values(this.canvas).forEach((canvas) => {
this.config = {};
// @ts-expect-error force delete
this.renderers = {};
Object.entries(this.canvas).forEach(([name, canvas]) => {
const camera = canvas.getCamera();
// @ts-expect-error landmark is private
if (camera.landmarks?.length) {
@ -261,6 +264,8 @@ export class Canvas {
}
canvas.destroy();
// @ts-expect-error force delete
this[name] = undefined;
});
}
}

View File

@ -759,7 +759,7 @@ export class DataController {
model.removeNodes(nodes.map((node) => node.id));
// @ts-expect-error force delete
delete this.context;
this.context = {};
}
}

View File

@ -718,14 +718,19 @@ export class ElementController {
}
public destroy() {
Object.values(this.elementMap).forEach((element) => element.destroy());
Object.values(this.container).forEach((container) => container.destroy());
// @ts-expect-error force delete
this.container = {};
this.elementMap = {};
this.shapeTypeMap = {};
this.defaultStyle = {};
this.stateStyle = {};
this.paletteStyle = {};
// @ts-expect-error force delete
delete this.context;
this.context = {};
// @ts-expect-error force delete
this.latestElementVisibilityMap = undefined;
}
}

View File

@ -503,16 +503,17 @@ export class Graph extends EventEmitter {
public destroy(): void {
const { layout, element, model, canvas, behavior, plugin } = this.context;
plugin?.destroy();
behavior?.destroy();
layout?.destroy();
element?.destroy();
model.destroy();
canvas?.destroy();
behavior?.destroy();
plugin?.destroy();
this.options = {};
// @ts-expect-error force delete
delete this.context;
this.context = {};
this.off();
window.removeEventListener('resize', this.onResize);
this.destroyed = true;

View File

@ -381,7 +381,11 @@ export class LayoutController {
public destroy() {
this.stopLayout();
// @ts-expect-error force delete
delete this.context;
this.context = {};
this.supervisor?.kill();
this.supervisor = undefined;
this.instance = undefined;
this.animationResult = undefined;
}
}