Skip to content

Commit a37fe28

Browse files
fix: Memory leaks on build if called more than once
1 parent c27dae6 commit a37fe28

File tree

9 files changed

+110
-42
lines changed

9 files changed

+110
-42
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gpu.js",
3-
"version": "2.8.4",
3+
"version": "2.8.5",
44
"description": "GPU Accelerated JavaScript",
55
"engines": {
66
"node": ">=8.0.0"

src/backend/cpu/kernel.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class CPUKernel extends Kernel {
136136
* <p>If the graphical flag is enabled, canvas is used.</p>
137137
*/
138138
build() {
139+
if (this.built) return;
139140
this.setupConstants();
140141
this.setupArguments(arguments);
141142
this.validateSettings(arguments);

src/backend/kernel.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,6 @@ class Kernel {
903903
*/
904904
static getSignature(kernel, argumentTypes) {
905905
throw new Error(`"getSignature" not implemented on ${ this.name }`);
906-
return argumentTypes.length > 0 ? ':' + argumentTypes.join(',') : '';
907906
}
908907

909908
/**

src/backend/web-gl/kernel.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ class WebGLKernel extends GLKernel {
468468
}
469469

470470
build() {
471+
if (this.built) return;
471472
this.initExtensions();
472473
this.validateSettings(arguments);
473474
this.setupConstants(arguments);
@@ -1454,6 +1455,7 @@ float integerCorrectionModulo(float number, float divisor) {
14541455
}
14551456

14561457
destroy(removeCanvasReferences) {
1458+
if (!this.context) return;
14571459
if (this.buffer) {
14581460
this.context.deleteBuffer(this.buffer);
14591461
}
@@ -1512,6 +1514,10 @@ float integerCorrectionModulo(float number, float divisor) {
15121514
this.destroyExtensions();
15131515
delete this.context;
15141516
delete this.canvas;
1517+
if (!this.gpu) return;
1518+
const i = this.gpu.kernels.indexOf(this);
1519+
if (i === -1) return;
1520+
this.gpu.kernels.splice(i, 1);
15151521
}
15161522

15171523
destroyExtensions() {

src/gpu.js

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -265,18 +265,18 @@ class GPU {
265265
*
266266
* @param {IReason[]} reasons
267267
* @param {IArguments} args
268-
* @param {Kernel} kernel
268+
* @param {Kernel} _kernel
269269
* @returns {*}
270270
*/
271-
function onRequestSwitchKernel(reasons, args, kernel) {
272-
if (kernel.debug) {
271+
function onRequestSwitchKernel(reasons, args, _kernel) {
272+
if (_kernel.debug) {
273273
console.warn('Switching kernels');
274274
}
275275
let newOutput = null;
276-
if (kernel.signature && !switchableKernels[kernel.signature]) {
277-
switchableKernels[kernel.signature] = kernel;
276+
if (_kernel.signature && !switchableKernels[_kernel.signature]) {
277+
switchableKernels[_kernel.signature] = _kernel;
278278
}
279-
if (kernel.dynamicOutput) {
279+
if (_kernel.dynamicOutput) {
280280
for (let i = reasons.length - 1; i >= 0; i--) {
281281
const reason = reasons[i];
282282
if (reason.type === 'outputPrecisionMismatch') {
@@ -285,42 +285,42 @@ class GPU {
285285
}
286286
}
287287

288-
const Constructor = kernel.constructor;
289-
const argumentTypes = Constructor.getArgumentTypes(kernel, args);
290-
const signature = Constructor.getSignature(kernel, argumentTypes);
288+
const Constructor = _kernel.constructor;
289+
const argumentTypes = Constructor.getArgumentTypes(_kernel, args);
290+
const signature = Constructor.getSignature(_kernel, argumentTypes);
291291
const existingKernel = switchableKernels[signature];
292292
if (existingKernel) {
293293
return existingKernel;
294294
}
295295

296296
const newKernel = switchableKernels[signature] = new Constructor(source, {
297297
argumentTypes,
298-
constantTypes: kernel.constantTypes,
299-
graphical: kernel.graphical,
300-
loopMaxIterations: kernel.loopMaxIterations,
301-
constants: kernel.constants,
302-
dynamicOutput: kernel.dynamicOutput,
303-
dynamicArgument: kernel.dynamicArguments,
304-
context: kernel.context,
305-
canvas: kernel.canvas,
306-
output: newOutput || kernel.output,
307-
precision: kernel.precision,
308-
pipeline: kernel.pipeline,
309-
immutable: kernel.immutable,
310-
optimizeFloatMemory: kernel.optimizeFloatMemory,
311-
fixIntegerDivisionAccuracy: kernel.fixIntegerDivisionAccuracy,
312-
functions: kernel.functions,
313-
nativeFunctions: kernel.nativeFunctions,
314-
injectedNative: kernel.injectedNative,
315-
subKernels: kernel.subKernels,
316-
strictIntegers: kernel.strictIntegers,
317-
debug: kernel.debug,
318-
gpu: kernel.gpu,
298+
constantTypes: _kernel.constantTypes,
299+
graphical: _kernel.graphical,
300+
loopMaxIterations: _kernel.loopMaxIterations,
301+
constants: _kernel.constants,
302+
dynamicOutput: _kernel.dynamicOutput,
303+
dynamicArgument: _kernel.dynamicArguments,
304+
context: _kernel.context,
305+
canvas: _kernel.canvas,
306+
output: newOutput || _kernel.output,
307+
precision: _kernel.precision,
308+
pipeline: _kernel.pipeline,
309+
immutable: _kernel.immutable,
310+
optimizeFloatMemory: _kernel.optimizeFloatMemory,
311+
fixIntegerDivisionAccuracy: _kernel.fixIntegerDivisionAccuracy,
312+
functions: _kernel.functions,
313+
nativeFunctions: _kernel.nativeFunctions,
314+
injectedNative: _kernel.injectedNative,
315+
subKernels: _kernel.subKernels,
316+
strictIntegers: _kernel.strictIntegers,
317+
debug: _kernel.debug,
318+
gpu: _kernel.gpu,
319319
validate,
320-
returnType: kernel.returnType,
321-
onIstanbulCoverageVariable: kernel.onIstanbulCoverageVariable,
322-
removeIstanbulCoverage: kernel.removeIstanbulCoverage,
323-
tactic: kernel.tactic,
320+
returnType: _kernel.returnType,
321+
onIstanbulCoverageVariable: _kernel.onIstanbulCoverageVariable,
322+
removeIstanbulCoverage: _kernel.removeIstanbulCoverage,
323+
tactic: _kernel.tactic,
324324
onRequestFallback,
325325
onRequestSwitchKernel,
326326
});
@@ -342,19 +342,20 @@ class GPU {
342342
onRequestSwitchKernel
343343
}, settingsCopy);
344344

345-
const kernelRun = kernelRunShortcut(new this.Kernel(source, mergedSettings));
345+
const kernel = new this.Kernel(source, mergedSettings);
346+
const kernelRun = kernelRunShortcut(kernel);
346347

347348
//if canvas didn't come from this, propagate from kernel
348349
if (!this.canvas) {
349-
this.canvas = kernelRun.canvas;
350+
this.canvas = kernel.canvas;
350351
}
351352

352353
//if context didn't come from this, propagate from kernel
353354
if (!this.context) {
354-
this.context = kernelRun.context;
355+
this.context = kernel.context;
355356
}
356357

357-
this.kernels.push(kernelRun);
358+
this.kernels.push(kernel);
358359

359360
return kernelRun;
360361
}

test/features/destroy.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ test('kernel.texture is deleted', (t) => {
113113
});
114114

115115
function testKernelMappedTexturesAreDeleted(done) {
116+
const mockGPU = {
117+
kernels: []
118+
};
116119
const webGLTexture1 = {};
117120
const mockTextureDelete1 = sinon.spy();
118121
const kernelTexture1 = {
@@ -127,22 +130,56 @@ function testKernelMappedTexturesAreDeleted(done) {
127130
};
128131
const mockContext = {};
129132
const mockKernelInstance = {
133+
gpu: mockGPU,
130134
mappedTextures: [kernelTexture1, kernelTexture2],
131135
textureCache: [],
132136
context: mockContext,
133137
destroyExtensions: () => {},
134138
};
139+
mockGPU.kernels.push(mockKernelInstance);
135140
mockKernelInstance.destroy = WebGLKernel.prototype.destroy.bind(mockKernelInstance);
136141
GPU.prototype.destroy.call({ kernels: [mockKernelInstance] })
137142
.then(() => {
138143
assert.equal(mockTextureDelete1.callCount, 1);
139144
assert.equal(mockTextureDelete2.callCount, 1);
145+
assert.equal(mockGPU.kernels.length, 0);
140146
assert.ok(true);
141147
done();
148+
})
149+
.catch((e) => {
150+
console.error(e);
142151
});
143152
}
144153

145154
test('kernel.mappedTextures are deleted', (t) => {
146155
const done = t.async();
147156
testKernelMappedTexturesAreDeleted(done);
148-
});
157+
});
158+
159+
test('gpu.kernels is populated and removed by kernel', () => {
160+
const gpu = new GPU();
161+
const kernel = gpu.createKernel(function() {
162+
return 1;
163+
});
164+
assert.equal(gpu.kernels.length, 1);
165+
assert.equal(gpu.kernels.indexOf(kernel.kernel), 0);
166+
kernel.destroy();
167+
assert.equal(gpu.kernels.length, 0);
168+
gpu.destroy();
169+
});
170+
171+
test('gpu.kernels is populated and removed by gpu', t => {
172+
const done = t.async();
173+
const gpu = new GPU();
174+
const kernel = gpu.createKernel(function() {
175+
return 1;
176+
});
177+
assert.equal(gpu.kernels.length, 1);
178+
assert.equal(gpu.kernels.indexOf(kernel.kernel), 0);
179+
180+
gpu.destroy()
181+
.then(() => {
182+
assert.equal(gpu.kernels.length, 0);
183+
done();
184+
});
185+
});

test/internal/backend/cpu-kernel.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const sinon = require('sinon');
2+
const { assert, skip, test, module: describe, only } = require('qunit');
3+
const { CPUKernel } = require('../../../src');
4+
5+
describe('internal: CPUKernel');
6+
7+
test('.build() checks if already built, and returns early if true', () => {
8+
const mockContext = {
9+
built: true,
10+
setupConstants: sinon.spy(),
11+
};
12+
CPUKernel.prototype.build.apply(mockContext);
13+
assert.equal(mockContext.setupConstants.callCount, 0);
14+
});

test/internal/backend/web-gl/kernel/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const sinon = require('sinon');
12
const { assert, skip, test, module: describe, only } = require('qunit');
23
const { WebGLKernel } = require('../../../../../src');
34

@@ -55,3 +56,12 @@ test('.validateSettings() checks output texture size - ok', () => {
5556
WebGLKernel.prototype.validateSettings.apply(mockContext, []);
5657
assert.ok(true);
5758
});
59+
60+
test('.build() checks if already built, and returns early if true', () => {
61+
const mockContext = {
62+
built: true,
63+
initExtensions: sinon.spy(),
64+
};
65+
WebGLKernel.prototype.build.apply(mockContext);
66+
assert.equal(mockContext.initExtensions.callCount, 0);
67+
});

0 commit comments

Comments
 (0)