Skip to content

Commit

Permalink
fix: another fix for memory leak of schema validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Łukasz Kużyński committed Sep 27, 2021
1 parent 5513d61 commit ded2a9b
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 26 deletions.
22 changes: 8 additions & 14 deletions packages/http/src/__tests__/memory-leak-prevention.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,35 @@ import { createClientFromOperations, PrismHttp } from '../client';
import { httpOperations } from './fixtures';
import { createTestLogger } from './test-helpers';

describe('Checks if memory leaks when handling of requests', () => {
describe('Checks if memory leaks', () => {
function round(client: PrismHttp) {
return client.post(
'/todos?overwrite=yes',
{
name: 'some name',
completed: false,
},
{ headers: { 'x-todos-publish': '2021-09-21T09:48:48.108Z' } }
);
return client.get('/todos', { headers: { 'x-todos-publish': '2021-09-21T09:48:48.108Z' } });
}

it('5k', () => {
it('when handling 5k of requests', () => {
const logger = createTestLogger();
const client = createClientFromOperations(httpOperations, {
validateRequest: true,
validateResponse: true,
checkSecurity: true,
errors: true,
mock: {
dynamic: true,
dynamic: false,
},
logger,
});

round(client);
const baseMemoryUsage = process.memoryUsage().heapUsed;
let minMemoryUsage = Infinity;

for (let i = 0; i < 5000; i++) {
round(client);
if (i % 100 === 0) {
global.gc();
minMemoryUsage = Math.min(baseMemoryUsage, process.memoryUsage().heapUsed);
}
}
expect(minMemoryUsage).toBeLessThanOrEqual(baseMemoryUsage);

global.gc();
expect(process.memoryUsage().heapUsed).toBeLessThanOrEqual(baseMemoryUsage * 1.02);
});
});
13 changes: 4 additions & 9 deletions packages/http/src/__tests__/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@ import * as pino from 'pino';
* Unfortunately disabled logger didn't work
*/
export function createTestLogger() {
const logger = pino({});

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
logger.info = noop;
logger.success = noop;
logger.error = noop;
logger.warn = noop;
logger.child = () => logger;
const logger = pino({
enabled: false,
});

logger.success = logger.info;
return logger;
}
21 changes: 18 additions & 3 deletions packages/http/src/validator/validators/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { validateAgainstSchema } from './utils';
import { ValidationContext, validateFn } from './types';

import { stripReadOnlyProperties, stripWriteOnlyProperties } from '../../utils/filterRequiredProperties';
import { JSONSchema7 } from 'json-schema';

export function deserializeFormBody(
schema: JSONSchema,
Expand Down Expand Up @@ -84,9 +85,23 @@ function deserializeAndValidate(content: IMediaTypeContent, schema: JSONSchema,
);
}

const normalizeSchemaProcessorMap: Record<ValidationContext, (schema: JSONSchema) => O.Option<JSONSchema>> = {
[ValidationContext.Input]: stripReadOnlyProperties,
[ValidationContext.Output]: stripWriteOnlyProperties,
function memoizeSchemaNormalizer(normalizer: SchemaNormalizer): SchemaNormalizer {
const cache = new WeakMap<JSONSchema7, O.Option<JSONSchema7>>();
return (schema: JSONSchema7) => {
const cached = cache.get(schema);
if (!cached) {
const newSchema = normalizer(schema);
cache.set(schema, newSchema);
return newSchema;
}
return cached;
};
}

type SchemaNormalizer = (schema: JSONSchema) => O.Option<JSONSchema>;
const normalizeSchemaProcessorMap: Record<ValidationContext, SchemaNormalizer> = {
[ValidationContext.Input]: memoizeSchemaNormalizer(stripReadOnlyProperties),
[ValidationContext.Output]: memoizeSchemaNormalizer(stripWriteOnlyProperties),
};

export const validate: validateFn<unknown, IMediaTypeContent> = (target, specs, context, mediaType, bundle) => {
Expand Down

0 comments on commit ded2a9b

Please sign in to comment.