-
Notifications
You must be signed in to change notification settings - Fork 672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce number of created temp dirs for profiles (closes #2735, closes #2013) #2740
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import path from 'path'; | ||
import tmp from 'tmp'; | ||
import TempDirectory from '../../../../utils/temp-directory'; | ||
import { writeFile } from '../../../../utils/promisified-functions'; | ||
|
||
|
||
|
@@ -54,11 +54,9 @@ async function generatePreferences (profileDir, { marionettePort, config }) { | |
} | ||
|
||
export default async function (runtimeInfo) { | ||
tmp.setGracefulCleanup(); | ||
const tmpDir = await TempDirectory.createDirectory('firefox-profile'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Solved in favor of top-level |
||
|
||
const tmpDir = tmp.dirSync({ unsafeCleanup: true }); | ||
|
||
await generatePreferences(tmpDir.name, runtimeInfo); | ||
await generatePreferences(tmpDir.path, runtimeInfo); | ||
|
||
return tmpDir; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { spawn } from 'child_process'; | ||
import OS from 'os-family'; | ||
import promisifyEvent from 'promisify-event'; | ||
import Promise from 'pinkie'; | ||
import { findProcess, killProcess } from './promisified-functions'; | ||
|
||
|
||
const BROWSER_CLOSING_TIMEOUT = 5; | ||
|
||
async function runWMIC (args) { | ||
const wmicProcess = spawn('wmic.exe', args, { detached: true }); | ||
|
||
let wmicOutput = ''; | ||
|
||
wmicProcess.stdout.on('data', data => { | ||
wmicOutput += data.toString(); | ||
}); | ||
|
||
try { | ||
await Promise.race([ | ||
promisifyEvent(wmicProcess.stdout, 'end'), | ||
promisifyEvent(wmicProcess, 'error') | ||
]); | ||
|
||
return wmicOutput; | ||
} | ||
catch (e) { | ||
return ''; | ||
} | ||
} | ||
|
||
async function findProcessWin (processOptions) { | ||
var wmicArgs = ['process', 'where', `commandline like '%${processOptions.arguments}%' and name <> 'cmd.exe' and name <> 'wmic.exe'`, 'get', 'processid']; | ||
var wmicOutput = await runWMIC(wmicArgs); | ||
var processList = wmicOutput.split(/\s*\n/); | ||
|
||
processList = processList | ||
// NOTE: remove list's header and empty last element, caused by trailing newline | ||
.slice(1, -1) | ||
.map(pid => ({ pid: Number(pid) })); | ||
|
||
return processList; | ||
} | ||
|
||
export default async function (browserId) { | ||
var processOptions = { arguments: browserId, psargs: '-ef' }; | ||
var processList = OS.win ? await findProcessWin(processOptions) : await findProcess(processOptions); | ||
|
||
if (!processList.length) | ||
return true; | ||
|
||
try { | ||
if (OS.win) | ||
process.kill(processList[0].pid); | ||
else | ||
await killProcess(processList[0].pid, { timeout: BROWSER_CLOSING_TIMEOUT }); | ||
|
||
return true; | ||
} | ||
catch (e) { | ||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default { | ||
init: 'init', | ||
add: 'add', | ||
remove: 'remove' | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { spawn } from 'child_process'; | ||
import debug from 'debug'; | ||
import promisifyEvent from 'promisify-event'; | ||
import Promise from 'pinkie'; | ||
import { sendMessageToChildProcess } from '../../promisified-functions'; | ||
import COMMANDS from './commands'; | ||
|
||
|
||
const WORKER_PATH = require.resolve('./worker'); | ||
const WORKER_STDIO_CONFIG = ['ignore', 'ignore', 'ignore', 'ipc']; | ||
|
||
const DEBUG_LOGGER = debug('testcafe:utils:temp-directory:cleanup-process'); | ||
|
||
class CleanupProcess { | ||
constructor () { | ||
this.worker = null; | ||
this.initialized = false; | ||
this.initPromise = Promise.resolve(void 0); | ||
|
||
this.messageCounter = 0; | ||
|
||
this.pendingResponses = {}; | ||
} | ||
|
||
_sendMessage (id, msg) { | ||
return sendMessageToChildProcess(this.worker, { id, ...msg }); | ||
} | ||
|
||
_onResponse (response) { | ||
const pendingResponse = this.pendingResponses[response.id]; | ||
|
||
if (response.error) { | ||
if (pendingResponse) | ||
pendingResponse.control.reject(response.error); | ||
else | ||
this.pendingResponses[response.id] = Promise.reject(response.error); | ||
} | ||
else if (pendingResponse) | ||
pendingResponse.control.resolve(); | ||
else | ||
this.pendingResponses[response.id] = Promise.resolve(); | ||
} | ||
|
||
async _waitResponse (id) { | ||
if (!this.pendingResponses[id]) { | ||
const promiseControl = {}; | ||
|
||
this.pendingResponses[id] = new Promise((resolve, reject) => { | ||
Object.assign(promiseControl, { resolve, reject }); | ||
}); | ||
|
||
this.pendingResponses[id].control = promiseControl; | ||
} | ||
|
||
try { | ||
await this.pendingResponses[id]; | ||
} | ||
finally { | ||
delete this.pendingResponses[id]; | ||
} | ||
} | ||
|
||
async _waitResponseForMessage (msg) { | ||
const currentId = this.messageCounter; | ||
|
||
this.messageCounter++; | ||
|
||
await this._sendMessage(currentId, msg); | ||
await this._waitResponse(currentId); | ||
} | ||
|
||
init () { | ||
this.initPromise = this.initPromise | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does it work? Is it the workaround for the concurrent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's a guard that ensures that |
||
.then(async initialized => { | ||
if (initialized !== void 0) | ||
return initialized; | ||
|
||
this.worker = spawn(process.argv[0], [WORKER_PATH], { detached: true, stdio: WORKER_STDIO_CONFIG }); | ||
|
||
this.worker.on('message', message => this._onResponse(message)); | ||
|
||
this.worker.unref(); | ||
|
||
try { | ||
await Promise.race([ | ||
this._waitResponseForMessage({ command: COMMANDS.init }), | ||
promisifyEvent(this.worker, 'error') | ||
]); | ||
|
||
const channel = this.worker.channel || this.worker._channel; | ||
|
||
channel.unref(); | ||
|
||
this.initialized = true; | ||
} | ||
catch (e) { | ||
DEBUG_LOGGER('Failed to start cleanup process'); | ||
DEBUG_LOGGER(e); | ||
|
||
this.initialized = false; | ||
} | ||
|
||
return this.initialized; | ||
}); | ||
|
||
return this.initPromise; | ||
} | ||
|
||
async addDirectory (path) { | ||
if (!this.initialized) | ||
return; | ||
|
||
try { | ||
await this._waitResponseForMessage({ command: COMMANDS.add, path }); | ||
} | ||
catch (e) { | ||
DEBUG_LOGGER(`Failed to add the ${path} directory to cleanup process`); | ||
DEBUG_LOGGER(e); | ||
} | ||
} | ||
|
||
async removeDirectory (path) { | ||
if (!this.initialized) | ||
return; | ||
|
||
try { | ||
await this._waitResponseForMessage({ command: COMMANDS.remove, path }); | ||
} | ||
catch (e) { | ||
DEBUG_LOGGER(`Failed to remove the ${path} directory in cleanup process`); | ||
DEBUG_LOGGER(e); | ||
} | ||
} | ||
} | ||
|
||
export default new CleanupProcess(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be
testcafe-chrome-profile
to avoid the name conflicts.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solved in favor of top-level
testcafe-tmp
.