Skip to content

Commit

Permalink
fix: store temporary files in unique folder to avoid race conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinko Bajric authored and sf-v committed Apr 7, 2022
1 parent 255151c commit 465ed44
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 7 deletions.
19 changes: 12 additions & 7 deletions packages/@best/agent/src/utils/benchmark-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,30 @@

import path from "path";
import SocketIOFile from "socket.io-file";
import { cacheDirectory } from '@best/utils';
import { cacheDirectory, randomAlphanumeric } from '@best/utils';
import { x as extractTar } from 'tar';
import { Socket } from "socket.io";

// This is all part of the initialization
const LOADER_CONFIG = {
uploadDir: path.join(cacheDirectory('best_agent'), 'uploads'),
const LOADER_CONFIG_DEFAULTS = {
accepts: [],
maxFileSize: 52428800, // 50 mb
chunkSize: 10240, // 10kb
transmissionDelay: 0,
overwrite: true,
};

// In order to make the uploader singleton, but yet allow multiple file downloads we need to do some manual cleanup
// The assumption is only one upload can occurr at the time, otherwise this code might not work as expected

export function getUploaderInstance(socket: Socket): SocketIOFile {
const uploader: any = new SocketIOFile(socket, LOADER_CONFIG);
// In case multiple agents are connected to the same hub and multiple benchmarks are invoked concurrently,
// if more than one benchmark has the exact same name, an error could occur because of a race condition.
// This race condition is triggered when one client is uploading to the hub while the hub is uploading
// same-named benchmark to the agent. When this happens, the agent may get a partial file or the hub may fail
// because there is a lock on the same-named file.
const config = Object.assign({}, LOADER_CONFIG_DEFAULTS, {
uploadDir: path.join(cacheDirectory('best_agent'), 'uploads', randomAlphanumeric(16))
});

const uploader: any = new SocketIOFile(socket, config);
uploader.load = function () {
return new Promise((resolve, reject) => {
uploader.on('complete', (info: any) => {
Expand Down
28 changes: 28 additions & 0 deletions packages/@best/utils/src/__tests__/random.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { randomAlphanumeric } from '../random';

describe('randomAlphanumeric', () => {
test('returns random string whose length matches requested length', () => {
for (let len = 0; len < 100; len ++) {
const str = randomAlphanumeric(len);
expect(str).toHaveLength(len);
}
});

test('returns empty string when length requested is less than 1', () => {
const str = randomAlphanumeric(0);
expect(str).toBe('');

});

test('returns empty string when length parameter is negative number', () => {
const str = randomAlphanumeric(-15);
expect(str).toBe('');
});
});
1 change: 1 addition & 0 deletions packages/@best/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export { proxifiedSocketOptions } from './proxy';
export { matchSpecs } from './match-specs';
export { RunnerInterruption } from './runner-interruption';
export { normalizeClientConfig, normalizeSpecs } from './normalize-client-config';
export { randomAlphanumeric } from './random';
32 changes: 32 additions & 0 deletions packages/@best/utils/src/random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

const ALPHANUMERIC_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const ALPHANUMERIC_CHARACTERS_LENGTH = ALPHANUMERIC_CHARACTERS.length;

/**
* Creates a random alphanumeric string whose length is the number of characters specified.
* @param length The length of the random string to create
* @returns The random string
*/
export const randomAlphanumeric = (length: Number): string => {
if (length < 1) {
return "";
}

let randomString = "";

for (let i = 0; i < length; i++) {
// Note: Math.random returns a decimal value between 0 (inclusive) and 1 (exclusive).
// Since it will never return a 1 and we are doing Math.floor here, the index will never
// be larger than (ALPHANUMERIC_CHARACTERS_LENGTH-1)
const index = Math.floor(Math.random() * ALPHANUMERIC_CHARACTERS_LENGTH);
randomString += ALPHANUMERIC_CHARACTERS[index];
}

return randomString;
}

0 comments on commit 465ed44

Please sign in to comment.