Skip to content

Commit 3d4a5e8

Browse files
build - v1.5.0
1 parent e07eae2 commit 3d4a5e8

File tree

7 files changed

+128
-75
lines changed

7 files changed

+128
-75
lines changed

dist/src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ async function createDB(opts) {
5252
ignoreUnsupportedSystemVersion: false,
5353
port: 0,
5454
xPort: 0,
55-
binaryDirectoryPath: `${os.tmpdir()}/mysqlmsn/binaries`
55+
binaryDirectoryPath: `${os.tmpdir()}/mysqlmsn/binaries`,
56+
downloadRetries: 10
5657
};
5758
const options = { ...defaultOptions, ...opts };
5859
const logger = new Logger_1.default(options.logLevel);

dist/src/libraries/Downloader.js

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,37 @@ function downloadVersions() {
7575
});
7676
}
7777
function downloadFromCDN(url, downloadLocation, logger) {
78-
return new Promise((resolve, reject) => {
78+
return new Promise(async (resolve, reject) => {
79+
if (fs.existsSync(downloadLocation)) {
80+
logger.warn('Removing item at downloadLocation:', downloadLocation, 'so the MySQL binary archive can be stored there. This is probably because a previous download/extraction failed.');
81+
await fsPromises.rm(downloadLocation, { recursive: true, force: true });
82+
}
7983
const fileStream = fs.createWriteStream(downloadLocation);
8084
let error;
8185
fileStream.on('open', () => {
8286
const request = https.get(url, (response) => {
83-
response.pipe(fileStream);
87+
if (response.statusCode !== 200) {
88+
fileStream.close((err) => {
89+
if (err) {
90+
logger.error('An error occurred while closing the fileStream for non-200 status code. The error was:', err);
91+
}
92+
fs.unlink(downloadLocation, (unlinkErr) => {
93+
if (unlinkErr) {
94+
logger.error('An error occurred while deleting downloadLocation after non-200 status code download attempt. The error was:', err);
95+
}
96+
logger.error('Received status code:', response.statusCode, 'while downloading MySQL binary.');
97+
reject(`Received status code ${response.statusCode} while downloading MySQL binary.`);
98+
});
99+
});
100+
}
101+
else {
102+
response.pipe(fileStream);
103+
fileStream.on('finish', () => {
104+
if (!error) {
105+
resolve();
106+
}
107+
});
108+
}
84109
});
85110
request.on('error', (err) => {
86111
error = err;
@@ -95,11 +120,6 @@ function downloadFromCDN(url, downloadLocation, logger) {
95120
});
96121
});
97122
});
98-
fileStream.on('finish', () => {
99-
if (!error) {
100-
resolve();
101-
}
102-
});
103123
fileStream.on('error', (err) => {
104124
error = err;
105125
logger.error(err);
@@ -116,6 +136,10 @@ function downloadFromCDN(url, downloadLocation, logger) {
116136
}
117137
function extractBinary(url, archiveLocation, extractedLocation, logger) {
118138
return new Promise(async (resolve, reject) => {
139+
if (fs.existsSync(extractedLocation)) {
140+
logger.warn('Removing item at extractedLocation:', extractedLocation, 'so the MySQL binary can be stored there. This is probably because a previous download/extraction failed.');
141+
await fsPromises.rm(extractedLocation, { recursive: true, force: true });
142+
}
119143
const lastDashIndex = url.lastIndexOf('-');
120144
const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.');
121145
await fsPromises.mkdir(extractedLocation, { recursive: true });
@@ -167,7 +191,8 @@ function extractBinary(url, archiveLocation, extractedLocation, logger) {
167191
resolve(`${extractedLocation}/mysql/bin/mysqld`);
168192
}
169193
}).catch(error => {
170-
reject(`An error occurred while extracting the tar file. Please make sure tar is installed and there is enough storage space for the extraction. The error was: ${error}`);
194+
logger.error(`An error occurred while extracting the tar file. Please make sure tar is installed and there is enough storage space for the extraction. The error was: ${error}`);
195+
reject(error);
171196
});
172197
});
173198
}
@@ -190,8 +215,7 @@ function downloadBinary(binaryInfo, options, logger) {
190215
let releaseFunction;
191216
while (true) {
192217
try {
193-
await fsPromises.mkdir(extractedPath, { recursive: true });
194-
releaseFunction = (0, proper_lockfile_1.lockSync)(extractedPath);
218+
releaseFunction = (0, proper_lockfile_1.lockSync)(extractedPath, { realpath: false });
195219
break;
196220
}
197221
catch (e) {
@@ -210,54 +234,82 @@ function downloadBinary(binaryInfo, options, logger) {
210234
}
211235
}
212236
//The code below only runs if the lock has been acquired by us
213-
try {
214-
await downloadFromCDN(url, archivePath, logger);
215-
await extractBinary(url, archivePath, extractedPath, logger);
216-
}
217-
catch (e) {
237+
let downloadTries = 0;
238+
do {
218239
try {
219-
await Promise.all([
220-
fsPromises.rm(extractedPath, { force: true, recursive: true }),
221-
fsPromises.rm(archivePath, { force: true, recursive: true })
222-
]);
240+
downloadTries++;
241+
await downloadFromCDN(url, archivePath, logger);
242+
await extractBinary(url, archivePath, extractedPath, logger);
243+
break;
223244
}
224245
catch (e) {
225-
logger.error('An error occurred while deleting extractedPath and/or archivePath:', e);
226-
}
227-
finally {
246+
//Delete generated files since either download or extraction failed
228247
try {
229-
releaseFunction();
248+
await Promise.all([
249+
fsPromises.rm(extractedPath, { force: true, recursive: true }),
250+
fsPromises.rm(archivePath, { force: true, recursive: true })
251+
]);
230252
}
231253
catch (e) {
232-
logger.error('An error occurred while unlocking path:', e);
254+
logger.error('An error occurred while deleting extractedPath and/or archivePath:', e);
255+
}
256+
if (downloadTries >= options.downloadRetries) {
257+
//Only reject if we have met the downloadRetries limit
258+
try {
259+
releaseFunction();
260+
}
261+
catch (e) {
262+
logger.error('An error occurred while releasing lock after downloadRetries exhaustion. The error was:', e);
263+
}
264+
logger.error('downloadRetries have been exceeded. Aborting download.');
265+
return reject(e);
266+
}
267+
else {
268+
console.warn(`An error was encountered during the binary download process. Retrying for retry ${downloadTries}/${options.downloadRetries}. The error was:`, e);
233269
}
234-
return reject(e);
235270
}
236-
}
271+
} while (downloadTries < options.downloadRetries);
237272
try {
238273
releaseFunction();
239274
}
240275
catch (e) {
241-
return reject(e);
276+
logger.error('An error occurred while releasing lock after successful binary download. The error was:', e);
242277
}
243278
return resolve(binaryPath);
244279
}
245-
const uuid = (0, crypto_1.randomUUID)();
246-
const zipFilepath = `${dirpath}/${uuid}.${fileExtension}`;
247-
logger.log('Binary filepath:', zipFilepath);
248-
const extractedPath = `${dirpath}/${uuid}`;
249-
try {
250-
await downloadFromCDN(url, zipFilepath, logger);
251-
}
252-
catch (e) {
253-
reject(e);
254-
}
255-
try {
256-
const binaryPath = await extractBinary(url, zipFilepath, extractedPath, logger);
257-
resolve(binaryPath);
258-
}
259-
catch (e) {
260-
reject(e);
280+
else {
281+
let downloadTries = 0;
282+
do {
283+
const uuid = (0, crypto_1.randomUUID)();
284+
const zipFilepath = `${dirpath}/${uuid}.${fileExtension}`;
285+
logger.log('Binary filepath:', zipFilepath);
286+
const extractedPath = `${dirpath}/${uuid}`;
287+
try {
288+
downloadTries++;
289+
await downloadFromCDN(url, zipFilepath, logger);
290+
const binaryPath = await extractBinary(url, zipFilepath, extractedPath, logger);
291+
return resolve(binaryPath);
292+
}
293+
catch (e) {
294+
//Delete generated files since either download or extraction failed
295+
try {
296+
await Promise.all([
297+
fsPromises.rm(extractedPath, { force: true, recursive: true }),
298+
fsPromises.rm(zipFilepath, { force: true, recursive: true })
299+
]);
300+
}
301+
catch (e) {
302+
logger.error('An error occurred while deleting extractedPath and/or archivePath:', e);
303+
}
304+
if (downloadTries >= options.downloadRetries) {
305+
//Only reject if we have met the downloadRetries limit
306+
return reject(e);
307+
}
308+
else {
309+
console.warn(`An error was encountered during the binary download process. Retrying for retry ${downloadTries}/${options.downloadRetries}. The error was:`, e);
310+
}
311+
}
312+
} while (downloadTries < options.downloadRetries);
261313
}
262314
});
263315
}

dist/src/libraries/Executor.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ declare class Executor {
44
#private;
55
logger: Logger;
66
constructor(logger: Logger);
7-
deleteDatabaseDirectory(path: string): Promise<void>;
87
getMySQLVersion(preferredVersion?: string): Promise<InstalledMySQLVersion | null>;
98
startMySQL(options: InternalServerOptions, binaryFilepath: string): Promise<MySQLDB>;
109
}

dist/src/libraries/Executor.js

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
3030
var __importDefault = (this && this.__importDefault) || function (mod) {
3131
return (mod && mod.__esModule) ? mod : { "default": mod };
3232
};
33-
var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
33+
var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_deleteDatabaseDirectory, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
3434
Object.defineProperty(exports, "__esModule", { value: true });
3535
const child_process_1 = require("child_process");
3636
const semver_1 = require("semver");
@@ -47,29 +47,6 @@ class Executor {
4747
_Executor_instances.add(this);
4848
this.logger = logger;
4949
}
50-
async deleteDatabaseDirectory(path) {
51-
let retries = 0;
52-
//Maximum wait of 10 seconds | 500ms * 20 retries = 10,000ms = 10 seconds
53-
const waitTime = 500;
54-
const maxRetries = 20;
55-
//Since the database processes are killed instantly (SIGKILL) sometimes the database file handles may still be open
56-
//This would cause an EBUSY error. Retrying the deletions for 10 seconds should give the OS enough time to close
57-
//the file handles.
58-
while (retries <= maxRetries) {
59-
try {
60-
await fsPromises.rm(path, { recursive: true, force: true });
61-
return;
62-
}
63-
catch (e) {
64-
if (retries === maxRetries) {
65-
throw e;
66-
}
67-
await new Promise(resolve => setTimeout(resolve, waitTime));
68-
retries++;
69-
this.logger.log('DB data directory deletion failed. Now on retry', retries);
70-
}
71-
}
72-
}
7350
getMySQLVersion(preferredVersion) {
7451
return new Promise(async (resolve, reject) => {
7552
if (process.platform === 'win32') {
@@ -182,6 +159,28 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
182159
killed = process.kill();
183160
}
184161
return killed;
162+
}, _Executor_deleteDatabaseDirectory = async function _Executor_deleteDatabaseDirectory(path) {
163+
let retries = 0;
164+
//Maximum wait of 10 seconds | 500ms * 20 retries = 10,000ms = 10 seconds
165+
const waitTime = 500;
166+
const maxRetries = 20;
167+
//Since the database processes are killed instantly (SIGKILL) sometimes the database file handles may still be open
168+
//This would cause an EBUSY error. Retrying the deletions for 10 seconds should give the OS enough time to close
169+
//the file handles.
170+
while (retries <= maxRetries) {
171+
try {
172+
await fsPromises.rm(path, { recursive: true, force: true });
173+
return;
174+
}
175+
catch (e) {
176+
if (retries === maxRetries) {
177+
throw e;
178+
}
179+
await new Promise(resolve => setTimeout(resolve, waitTime));
180+
retries++;
181+
this.logger.log('DB data directory deletion failed. Now on retry', retries);
182+
}
183+
}
185184
}, _Executor_startMySQLProcess = function _Executor_startMySQLProcess(options, port, mySQLXPort, datadir, dbPath, binaryFilepath) {
186185
const errors = [];
187186
const logFile = `${dbPath}/log.log`;
@@ -207,7 +206,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
207206
if (portIssue || xPortIssue) {
208207
this.logger.log('Error log when exiting for port in use error:', errorLog);
209208
try {
210-
await this.deleteDatabaseDirectory(options.dbPath);
209+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options.dbPath);
211210
}
212211
catch (e) {
213212
this.logger.error(e);
@@ -217,7 +216,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
217216
}
218217
try {
219218
if (options.deleteDBAfterStopped) {
220-
await this.deleteDatabaseDirectory(dbPath);
219+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, dbPath);
221220
}
222221
}
223222
catch (e) {
@@ -407,7 +406,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
407406
}
408407
//Retry setting up directory now that libaio has been copied
409408
this.logger.log('Retrying directory setup');
410-
await this.deleteDatabaseDirectory(datadir);
409+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, datadir);
411410
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, false);
412411
return;
413412
}

dist/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type ServerOptions = {
1515
port?: number;
1616
xPort?: number;
1717
binaryDirectoryPath?: string;
18+
downloadRetries?: number;
1819
};
1920
export type InternalServerOptions = {
2021
version?: string;
@@ -31,6 +32,7 @@ export type InternalServerOptions = {
3132
port: number;
3233
xPort: number;
3334
binaryDirectoryPath: string;
35+
downloadRetries: number;
3436
};
3537
export type ExecutorOptions = {
3638
logLevel: LOG_LEVEL;

package-lock.json

Lines changed: 2 additions & 2 deletions
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": "mysql-memory-server",
3-
"version": "1.4.3",
3+
"version": "1.5.0",
44
"description": "Spin up an ephemeral MySQL database from your JavaScript code",
55
"main": "dist/src/index.js",
66
"types": "dist/src/index.d.ts",

0 commit comments

Comments
 (0)