Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions client/platform/desktop/backend/native/linux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
DesktopJobUpdater,
ConversionArgs,
} from 'platform/desktop/constants';

import { observeChild } from 'platform/desktop/backend/native/processManager';
import * as viame from './viame';
import { spawnResult } from './utils';

Expand Down Expand Up @@ -77,10 +77,10 @@ async function validateViamePath(settings: Settings): Promise<true | string> {
return `${trainingScriptPath} does not exist`;
}

const kwiverExistsOnPath = spawn(
const kwiverExistsOnPath = observeChild(spawn(
`source ${setupScriptPath} && which ${ViameLinuxConstants.kwiverExe}`,
{ shell: '/bin/bash' },
);
));
return new Promise((resolve) => {
kwiverExistsOnPath.on('exit', (code) => {
if (code === 0) {
Expand Down Expand Up @@ -119,12 +119,12 @@ async function train(
// Based on https://github.com/chrisallenlane/node-nvidia-smi
async function nvidiaSmi(): Promise<NvidiaSmiReply> {
return new Promise((resolve) => {
const smi = spawn('nvidia-smi', ['-q', '-x']);
const smi = observeChild(spawn('nvidia-smi', ['-q', '-x']));
let result = '';
smi.stdout.on('data', (chunk) => {
result = result.concat(chunk.toString('utf-8'));
});
smi.on('close', (exitCode) => {
smi.on('exit', (exitCode) => {
let jsonStr = 'null'; // parses to null
if (exitCode === 0) {
jsonStr = xml2json(result, { compact: true });
Expand Down
52 changes: 52 additions & 0 deletions client/platform/desktop/backend/native/processManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* ProcessManager keeps track of the subprocesses opened
* by varios parts of the application and provides controls
* to make sure they exit.
*/

import { ChildProcess } from 'child_process';

const children: ChildProcess[] = [];

/**
* Add child to list of outstanding processes,
* remove it when the process exits
*/
function observeChild<T extends ChildProcess>(child: T) {
if (child.exitCode === null) {
children.push(child);
child.on('exit', () => {
children.splice(children.indexOf(child), 1);
});
}
return child;
}

/**
* close a child, and return a promise
*/
function close(child: ChildProcess): Promise<void> {
const onclose = new Promise<void>((resolve) => {
child.on('exit', (code, signal) => {
console.warn(`pid=${child.pid} exited with code=${code} and signal=${signal}`);
resolve();
});
});

child.kill('SIGTERM');
return onclose;
}

/**
* Stop all remaining child processes
*/
function closeAll() {
console.warn(`killing ${children.length} remaining child processes`);
return Promise.all(children.slice().map((child) => close(child)));
}

export {
observeChild,
close,
closeAll,
};
7 changes: 4 additions & 3 deletions client/platform/desktop/backend/native/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { spawn } from 'child_process';
import fs from 'fs-extra';
import { observeChild } from 'platform/desktop/backend/native/processManager';
import { DesktopJob, DesktopJobUpdater } from 'platform/desktop/constants';

/**
Expand Down Expand Up @@ -49,9 +50,9 @@ function jobFileEchoMiddleware(
}

function spawnResult(command: string, shell: boolean | string, args: string[] = []):
Promise<{ output: null | string; exitCode: number; error: string}> {
Promise<{ output: null | string; exitCode: number | null; error: string}> {
return new Promise((resolve) => {
const proc = spawn(command, args, { shell });
const proc = observeChild(spawn(command, args, { shell }));
let output = '';
let error = '';
proc.stdout.on('data', (chunk) => {
Expand All @@ -62,7 +63,7 @@ Promise<{ output: null | string; exitCode: number; error: string}> {
error = error.concat(chunk.toString('utf-8'));
});

proc.on('close', (exitCode) => {
proc.on('exit', (exitCode) => {
resolve({
output,
exitCode,
Expand Down
12 changes: 7 additions & 5 deletions client/platform/desktop/backend/native/viame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
DesktopJobUpdater,
} from 'platform/desktop/constants';
import { serialize } from 'platform/desktop/backend/serializers/viame';
import { observeChild } from 'platform/desktop/backend/native/processManager';

import * as common from './common';
import { cleanString, jobFileEchoMiddleware, spawnResult } from './utils';


const PipelineRelativeDir = 'configs/pipelines';
const DiveJobManifestName = 'dive_job_manifest.json';

Expand Down Expand Up @@ -115,10 +117,10 @@ async function runPipeline(
}
}

const job = spawn(command.join(' '), {
const job = observeChild(spawn(command.join(' '), {
shell: viameConstants.shell,
cwd: jobWorkDir,
});
}));

const jobBase: DesktopJob = {
key: `pipeline_${job.pid}_${jobWorkDir}`,
Expand Down Expand Up @@ -251,10 +253,10 @@ async function train(
'--no-embedded-pipe',
];

const job = spawn(command.join(' '), {
const job = observeChild(spawn(command.join(' '), {
shell: viameConstants.shell,
cwd: jobWorkDir,
});
}));

const cleanPipelineName = cleanString(runTrainingArgs.pipelineName);

Expand Down Expand Up @@ -362,7 +364,7 @@ async function convertMedia(settings: Settings,
commands.push(`${viameConstants.ffmpeg.initialization} ${viameConstants.ffmpeg.path} -i "${args.mediaList[imageIndex][0]}" "${args.mediaList[imageIndex][1]}"`);
}

const job = spawn(commands.join(' '), { shell: viameConstants.shell });
const job = observeChild(spawn(commands.join(' '), { shell: viameConstants.shell }));
let jobKey = `convert_${job.pid}_${jobWorkDir}`;
if (key.length) {
jobKey = key;
Expand Down
19 changes: 12 additions & 7 deletions client/platform/desktop/backend/native/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { spawn } from 'child_process';
import fs from 'fs-extra';
import { xml2json } from 'xml-js';

import { observeChild } from 'platform/desktop/backend/native/processManager';
import {
Settings, SettingsCurrentVersion,
DesktopJob, RunPipeline, NvidiaSmiReply, RunTraining,
Expand Down Expand Up @@ -52,7 +53,7 @@ let programFiles = 'C:\\Program Files';
// There exists no app.getPath('programfiles') so we need to
// check the variable for the default location
async function initialize() {
const environmentVarPath = spawn('cmd.exe', ['/c', 'echo %PROGRAMFILES%'], { shell: true });
const environmentVarPath = observeChild(spawn('cmd.exe', ['/c', 'echo %PROGRAMFILES%'], { shell: true }));
environmentVarPath.stdout.on('data', (data) => {
const trimmed = data.toString().trim();
programFiles = trimmed;
Expand All @@ -68,11 +69,11 @@ async function validateViamePath(settings: Settings): Promise<true | string> {
}

const modifiedCommand = `"${setupScriptPath.replace(/\\/g, '\\')}"`;
const kwiverExistsOnPath = spawn(
const kwiverExistsOnPath = observeChild(spawn(
`${modifiedCommand} && kwiver.exe help`, {
shell: true,
},
);
));
return new Promise((resolve) => {
kwiverExistsOnPath.on('exit', (code) => {
if (code === 0) {
Expand Down Expand Up @@ -111,13 +112,17 @@ async function train(
}

function checkDefaultNvidiaSmi(resolve: (value: NvidiaSmiReply) => void) {
const smi = spawn(`"${programFiles}\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe"`, ['-q', '-x'], { shell: true });
const smi = observeChild(spawn(
`"${programFiles}\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe"`,
['-q', '-x'],
{ shell: true },
));
let result = '';
smi.stdout.on('data', (chunk) => {
result = result.concat(chunk.toString('utf-8'));
});

smi.on('close', (exitCode) => {
smi.on('exit', (exitCode) => {
let jsonStr = 'null'; // parses to null
if (exitCode === 0) {
jsonStr = xml2json(result, { compact: true });
Expand All @@ -141,15 +146,15 @@ function checkDefaultNvidiaSmi(resolve: (value: NvidiaSmiReply) => void) {
// it doesn't guarantee that the system doesn't have a relevant GPU
async function nvidiaSmi(): Promise<NvidiaSmiReply> {
return new Promise((resolve) => {
const pathsmi = spawn('nvidia-smi', ['-q', '-x'], { shell: true });
const pathsmi = observeChild(spawn('nvidia-smi', ['-q', '-x'], { shell: true }));
let result = '';
pathsmi.stdout.on('data', (chunk) => {
// eslint-disable-next-line no-console
console.log(chunk.toString('utf-8'));
result = result.concat(chunk.toString('utf-8'));
});

pathsmi.on('close', (exitCode) => {
pathsmi.on('exit', (exitCode) => {
let jsonStr = 'null'; // parses to null
if (exitCode === 0) {
jsonStr = xml2json(result, { compact: true });
Expand Down
36 changes: 21 additions & 15 deletions client/platform/desktop/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';

import { listen, close } from './backend/server';
import { closeAll as closeChildren } from './backend/native/processManager';
import { listen, close as closeServer } from './backend/server';
import ipcListen from './backend/ipcService';

app.commandLine.appendSwitch('no-sandbox');
Expand All @@ -22,8 +23,9 @@ protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } },
]);

function cleanup() {
close();
async function cleanup() {
closeServer();
await closeChildren();
app.quit();
}

Expand Down Expand Up @@ -87,6 +89,13 @@ app.on('activate', () => {
}
});

// If the quit button from the context menu is used,
// Intercept the before-quit event
app.on('before-quit', () => {
closeChildren();
closeServer();
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
Expand All @@ -102,17 +111,14 @@ app.on('ready', async () => {
createWindow();
});

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
cleanup();
}
});
} else {
process.on('SIGTERM', () => {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
cleanup();
});
}
}
});
} else {
process.on('SIGTERM', () => {
cleanup();
});
}
2 changes: 1 addition & 1 deletion client/platform/desktop/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export interface NvidiaSmiReply {
};
} | null;
// process exit code
exitCode: number;
exitCode: number | null;
// error message
error: string;
}
Expand Down