Skip to content

Commit d1110ef

Browse files
Merge pull request #4779 from NativeScript/kddimitrov/fix-hanging-webpack-process
Kddimitrov/fix hanging webpack process
2 parents d14cdaf + 97cacbf commit d1110ef

File tree

8 files changed

+101
-25
lines changed

8 files changed

+101
-25
lines changed

lib/bash-scripts/terminateProcess.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
terminateTree() {
4+
for cpid in $(/usr/bin/pgrep -P $1); do
5+
terminateTree $cpid
6+
done
7+
kill -9 $1 > /dev/null 2>&1
8+
}
9+
10+
for pid in $*; do
11+
terminateTree $pid
12+
done

lib/controllers/prepare-controller.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class PrepareController extends EventEmitter {
5555
return result;
5656
}
5757

58-
public stopWatchers(projectDir: string, platform: string): void {
58+
public async stopWatchers(projectDir: string, platform: string): Promise<void> {
5959
const platformLowerCase = platform.toLowerCase();
6060

6161
if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) {
@@ -64,7 +64,7 @@ export class PrepareController extends EventEmitter {
6464
}
6565

6666
if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) {
67-
this.$webpackCompilerService.stopWebpackCompiler(platform);
67+
await this.$webpackCompilerService.stopWebpackCompiler(platform);
6868
this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null;
6969
}
7070
}

lib/controllers/run-controller.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,23 @@ export class RunController extends EventEmitter implements IRunController {
6868
.map(descriptor => descriptor.identifier);
6969

7070
// Handle the case when no more devices left for any of the persisted platforms
71-
_.each(liveSyncProcessInfo.platforms, platform => {
71+
for (let i = 0; i < liveSyncProcessInfo.platforms.length; i++) {
72+
const platform = liveSyncProcessInfo.platforms[i];
7273
const devices = this.$devicesService.getDevicesForPlatform(platform);
7374
if (!devices || !devices.length) {
74-
this.$prepareController.stopWatchers(projectDir, platform);
75+
await this.$prepareController.stopWatchers(projectDir, platform);
7576
}
76-
});
77+
}
7778

7879
// In case deviceIdentifiers are not passed, we should stop the whole LiveSync.
7980
if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) {
8081
if (liveSyncProcessInfo.timer) {
8182
clearTimeout(liveSyncProcessInfo.timer);
8283
}
8384

84-
_.each(liveSyncProcessInfo.platforms, platform => {
85-
this.$prepareController.stopWatchers(projectDir, platform);
86-
});
85+
for (let k = 0; k < liveSyncProcessInfo.platforms.length; k++) {
86+
await this.$prepareController.stopWatchers(projectDir, liveSyncProcessInfo.platforms[k]);
87+
}
8788

8889
liveSyncProcessInfo.isStopped = true;
8990

lib/definitions/cleanup-service.d.ts

+14
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,18 @@ interface ICleanupService extends IShouldDispose, IDisposable {
5656
* @returns {Promise<void>}
5757
*/
5858
removeCleanupJS(jsCommand: IJSCommand): Promise<void>;
59+
60+
/**
61+
* Adds a kill action for the process
62+
* @param pid the pid of the process to be killed
63+
* @returns {Promise<void>}
64+
*/
65+
addKillProcess(pid: string): Promise<void>;
66+
67+
/**
68+
* Removes the kill action for the process
69+
* @param pid the pid of the process to be killed
70+
* @returns {Promise<void>}
71+
*/
72+
removeKillProcess(pid: string): Promise<void>;
5973
}

lib/definitions/prepare.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ declare global {
2323

2424
interface IPrepareController extends EventEmitter {
2525
prepare(prepareData: IPrepareData): Promise<IPrepareResultData>;
26-
stopWatchers(projectDir: string, platform: string): void;
26+
stopWatchers(projectDir: string, platform: string): Promise<void>;
2727
}
2828

2929
interface IPrepareResultData {

lib/services/cleanup-service.ts

+31
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ export class CleanupService implements ICleanupService {
4545
cleanupProcess.send(<IJSCleanupMessage>{ messageType: CleanupProcessMessage.RemoveJSFileToRequire, jsCommand});
4646
}
4747

48+
public async addKillProcess(pid: string): Promise<void> {
49+
const killSpawnCommandInfo = this.getKillProcesSpawnInfo(pid);
50+
await this.addCleanupCommand(killSpawnCommandInfo);
51+
}
52+
53+
public async removeKillProcess(pid: string): Promise<void> {
54+
const killSpawnCommandInfo = this.getKillProcesSpawnInfo(pid);
55+
await this.removeCleanupCommand(killSpawnCommandInfo);
56+
}
57+
4858
@exported("cleanupService")
4959
public setCleanupLogFile(filePath: string): void {
5060
this.pathToCleanupLogFile = filePath;
@@ -121,6 +131,27 @@ export class CleanupService implements ICleanupService {
121131

122132
return cleanupProcessArgs;
123133
}
134+
135+
private getKillProcesSpawnInfo(pid: string): ISpawnCommandInfo {
136+
let command;
137+
let args;
138+
switch (process.platform) {
139+
case 'win32':
140+
command = "taskkill";
141+
args = ["/pid", pid, "/T", "/F"];
142+
break;
143+
144+
default:
145+
command = path.join(__dirname, '../bash-scripts/terminateProcess.sh');
146+
args = [pid];
147+
break;
148+
}
149+
150+
return {
151+
command,
152+
args
153+
};
154+
}
124155
}
125156

126157
$injector.register("cleanupService", CleanupService);

lib/services/webpack/webpack-compiler-service.ts

+33-15
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
1414
public $hostInfo: IHostInfo,
1515
private $logger: ILogger,
1616
private $pluginsService: IPluginsService,
17-
private $mobileHelper: Mobile.IMobileHelper
17+
private $mobileHelper: Mobile.IMobileHelper,
18+
private $cleanupService: ICleanupService
1819
) { super(); }
1920

2021
public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise<any> {
@@ -70,15 +71,19 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
7071
}
7172
});
7273

73-
childProcess.on("close", (arg: any) => {
74+
childProcess.on("error", (err) => {
75+
this.$logger.trace(`Unable to start webpack process in watch mode. Error is: ${err}`);
76+
reject(err);
77+
});
78+
79+
childProcess.on("close", async (arg: any) => {
80+
await this.$cleanupService.removeKillProcess(childProcess.pid.toString());
81+
7482
const exitCode = typeof arg === "number" ? arg : arg && arg.code;
75-
if (exitCode === 0) {
76-
resolve(childProcess);
77-
} else {
78-
const error = new Error(`Executing webpack failed with exit code ${exitCode}.`);
79-
error.code = exitCode;
80-
reject(error);
81-
}
83+
this.$logger.trace(`Webpack process exited with code ${exitCode} when we expected it to be long living with watch.`);
84+
const error = new Error(`Executing webpack failed with exit code ${exitCode}.`);
85+
error.code = exitCode;
86+
reject(error);
8287
});
8388
});
8489
}
@@ -91,7 +96,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
9196
}
9297

9398
const childProcess = await this.startWebpackProcess(platformData, projectData, prepareData);
94-
childProcess.on("close", (arg: any) => {
99+
childProcess.on("error", (err) => {
100+
this.$logger.trace(`Unable to start webpack process in non-watch mode. Error is: ${err}`);
101+
reject(err);
102+
});
103+
104+
childProcess.on("close", async (arg: any) => {
105+
await this.$cleanupService.removeKillProcess(childProcess.pid.toString());
106+
95107
const exitCode = typeof arg === "number" ? arg : arg && arg.code;
96108
if (exitCode === 0) {
97109
resolve();
@@ -104,11 +116,15 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
104116
});
105117
}
106118

107-
public stopWebpackCompiler(platform: string): void {
119+
public async stopWebpackCompiler(platform: string): Promise<void> {
108120
if (platform) {
109-
this.stopWebpackForPlatform(platform);
121+
await this.stopWebpackForPlatform(platform);
110122
} else {
111-
Object.keys(this.webpackProcesses).forEach(pl => this.stopWebpackForPlatform(pl));
123+
const webpackedPlatforms = Object.keys(this.webpackProcesses);
124+
125+
for (let i = 0; i < webpackedPlatforms.length; i++) {
126+
await this.stopWebpackForPlatform(webpackedPlatforms[i]);
127+
}
112128
}
113129
}
114130

@@ -136,9 +152,10 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
136152
}
137153

138154
const stdio = prepareData.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit";
139-
const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio });
155+
const childProcess = this.$childProcess.spawn(process.execPath, args, { cwd: projectData.projectDir, stdio });
140156

141157
this.webpackProcesses[platformData.platformNameLowerCase] = childProcess;
158+
await this.$cleanupService.addKillProcess(childProcess.pid.toString());
142159

143160
return childProcess;
144161
}
@@ -233,9 +250,10 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
233250
};
234251
}
235252

236-
private stopWebpackForPlatform(platform: string) {
253+
private async stopWebpackForPlatform(platform: string) {
237254
this.$logger.trace(`Stopping webpack watch for platform ${platform}.`);
238255
const webpackProcess = this.webpackProcesses[platform];
256+
await this.$cleanupService.removeKillProcess(webpackProcess.pid.toString());
239257
if (webpackProcess) {
240258
webpackProcess.kill("SIGINT");
241259
delete this.webpackProcesses[platform];

lib/services/webpack/webpack.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ declare global {
66
interface IWebpackCompilerService extends EventEmitter {
77
compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise<any>;
88
compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise<void>;
9-
stopWebpackCompiler(platform: string): void;
9+
stopWebpackCompiler(platform: string): Promise<void>;
1010
}
1111

1212
interface IWebpackEnvOptions {

0 commit comments

Comments
 (0)