Skip to content

Commit 465ed44

Browse files
Dinko Bajricsf-v
Dinko Bajric
authored andcommitted
fix: store temporary files in unique folder to avoid race conditions
1 parent 255151c commit 465ed44

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

packages/@best/agent/src/utils/benchmark-loader.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,30 @@
77

88
import path from "path";
99
import SocketIOFile from "socket.io-file";
10-
import { cacheDirectory } from '@best/utils';
10+
import { cacheDirectory, randomAlphanumeric } from '@best/utils';
1111
import { x as extractTar } from 'tar';
1212
import { Socket } from "socket.io";
1313

1414
// This is all part of the initialization
15-
const LOADER_CONFIG = {
16-
uploadDir: path.join(cacheDirectory('best_agent'), 'uploads'),
15+
const LOADER_CONFIG_DEFAULTS = {
1716
accepts: [],
1817
maxFileSize: 52428800, // 50 mb
1918
chunkSize: 10240, // 10kb
2019
transmissionDelay: 0,
2120
overwrite: true,
2221
};
2322

24-
// In order to make the uploader singleton, but yet allow multiple file downloads we need to do some manual cleanup
25-
// The assumption is only one upload can occurr at the time, otherwise this code might not work as expected
26-
2723
export function getUploaderInstance(socket: Socket): SocketIOFile {
28-
const uploader: any = new SocketIOFile(socket, LOADER_CONFIG);
24+
// In case multiple agents are connected to the same hub and multiple benchmarks are invoked concurrently,
25+
// if more than one benchmark has the exact same name, an error could occur because of a race condition.
26+
// This race condition is triggered when one client is uploading to the hub while the hub is uploading
27+
// same-named benchmark to the agent. When this happens, the agent may get a partial file or the hub may fail
28+
// because there is a lock on the same-named file.
29+
const config = Object.assign({}, LOADER_CONFIG_DEFAULTS, {
30+
uploadDir: path.join(cacheDirectory('best_agent'), 'uploads', randomAlphanumeric(16))
31+
});
32+
33+
const uploader: any = new SocketIOFile(socket, config);
2934
uploader.load = function () {
3035
return new Promise((resolve, reject) => {
3136
uploader.on('complete', (info: any) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2022, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
import { randomAlphanumeric } from '../random';
9+
10+
describe('randomAlphanumeric', () => {
11+
test('returns random string whose length matches requested length', () => {
12+
for (let len = 0; len < 100; len ++) {
13+
const str = randomAlphanumeric(len);
14+
expect(str).toHaveLength(len);
15+
}
16+
});
17+
18+
test('returns empty string when length requested is less than 1', () => {
19+
const str = randomAlphanumeric(0);
20+
expect(str).toBe('');
21+
22+
});
23+
24+
test('returns empty string when length parameter is negative number', () => {
25+
const str = randomAlphanumeric(-15);
26+
expect(str).toBe('');
27+
});
28+
});

packages/@best/utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export { proxifiedSocketOptions } from './proxy';
1414
export { matchSpecs } from './match-specs';
1515
export { RunnerInterruption } from './runner-interruption';
1616
export { normalizeClientConfig, normalizeSpecs } from './normalize-client-config';
17+
export { randomAlphanumeric } from './random';

packages/@best/utils/src/random.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2022, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
const ALPHANUMERIC_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
9+
const ALPHANUMERIC_CHARACTERS_LENGTH = ALPHANUMERIC_CHARACTERS.length;
10+
11+
/**
12+
* Creates a random alphanumeric string whose length is the number of characters specified.
13+
* @param length The length of the random string to create
14+
* @returns The random string
15+
*/
16+
export const randomAlphanumeric = (length: Number): string => {
17+
if (length < 1) {
18+
return "";
19+
}
20+
21+
let randomString = "";
22+
23+
for (let i = 0; i < length; i++) {
24+
// Note: Math.random returns a decimal value between 0 (inclusive) and 1 (exclusive).
25+
// Since it will never return a 1 and we are doing Math.floor here, the index will never
26+
// be larger than (ALPHANUMERIC_CHARACTERS_LENGTH-1)
27+
const index = Math.floor(Math.random() * ALPHANUMERIC_CHARACTERS_LENGTH);
28+
randomString += ALPHANUMERIC_CHARACTERS[index];
29+
}
30+
31+
return randomString;
32+
}

0 commit comments

Comments
 (0)