Unverified Commit f556f4b8 authored by Svatopluk Dedic's avatar Svatopluk Dedic Committed by GitHub
Browse files

Prevent race conditions during CLI install (#2523)



* Use a special exitcode when connecting to CLI as client. Do not run upgrade step thereafter.

* Piggyback with NBJLS server relaunch after installation completes.

* Delay and/or disable the restart LSP client after connection termination
with the hope to finish install tasks meanwhile and then restart explicitly

* Wait after last child closes on Win.

* Fixed launchers release number -> release.
Co-authored-by: default avatarJaroslav Tulach <jaroslav.tulach@oracle.com>
parent 3a91c483
......@@ -3,9 +3,9 @@ build.xml.script.CRC32=95ec0861
build.xml.stylesheet.CRC32=70ce5c94@2.80
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=99f9616a
nbproject/build-impl.xml.data.CRC32=48c0390b
nbproject/build-impl.xml.script.CRC32=e03e5352
nbproject/build-impl.xml.stylesheet.CRC32=473dc988@2.80
nbproject/build-impl.xml.stylesheet.CRC32=473dc988@2.82
nbproject/platform.xml.data.CRC32=99f9616a
nbproject/platform.xml.script.CRC32=6dcbd131
nbproject/platform.xml.stylesheet.CRC32=ae64f0b6@2.80
......@@ -46,9 +46,9 @@
"default": false,
"description": "Enables verbose messages from the Apache NetBeans Language Server"
},
"netbeans.conflict.check" : {
"type" : "boolean",
"default" : true,
"netbeans.conflict.check": {
"type": "boolean",
"default": true,
"description": "Avoid conflicts with other Java extensions"
},
"java.test.editor.enableShortcuts": {
......
......@@ -168,13 +168,73 @@ export function activate(context: ExtensionContext) {
}));
}
/**
* Pending maintenance (install) task, activations should be chained after it.
*/
let maintenance : Promise<void> | null;
/**
* Pending activation flag. Will be cleared when the process produces some message or fails.
*/
let activationPending : boolean = false;
function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
if (nbProcess) {
const a : Promise<void> | null = maintenance;
if (activationPending) {
// do not activate more than once in parallel.
console.log("Server activation requested repeatedly, ignoring...");
return;
}
activationPending = true;
// chain the restart after termination of the former process.
if (a != null) {
console.log("Server activation initiated while in maintenance mode, scheduling after maintenance");
a.then(() => killNbProcess(notifyKill, log)).
then(() => doActivateWithJDK(specifiedJDK, context, log, notifyKill));
} else {
console.log("Initiating server activation");
killNbProcess(notifyKill, log).then(
() => doActivateWithJDK(specifiedJDK, context, log, notifyKill)
);
}
}
function killNbProcess(notifyKill : boolean, log : vscode.OutputChannel, specProcess?: ChildProcess) : Promise<void> {
const p = nbProcess;
console.log("Request to kill LSP server.");
if (p && (!specProcess || specProcess == p)) {
if (notifyKill) {
vscode.window.setStatusBarMessage("Restarting Apache NetBeans Language Server.", 2000);
}
nbProcess.kill();
return new Promise((resolve, reject) => {
nbProcess = null;
p.on('close', function(code: number) {
console.log("LSP server closed: " + p.pid)
resolve();
});
console.log("Killing LSP server " + p.pid);
if (!p.kill()) {
reject("Cannot kill");
}
});
} else {
let msg = "Cannot kill: ";
if (specProcess) {
msg += "Requested kill on " + specProcess.pid + ", ";
}
console.log(msg + "current process is " + (p ? p.pid : "None"));
return new Promise((res, rej) => { res(); });
}
}
function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
maintenance = null;
let restartWithJDKLater : ((time: number, n: boolean) => void) = function restartLater(time: number, n : boolean) {
log.appendLine(`Restart of Apache Language Server requested in ${(time / 1000)} s.`);
setTimeout(() => {
activateWithJDK(specifiedJDK, context, log, n);
}, time);
};
const beVerbose : boolean = workspace.getConfiguration('netbeans').get('verbose', false);
let info = {
......@@ -184,7 +244,6 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
jdkHome : specifiedJDK,
verbose: beVerbose
};
let launchMsg = `Launching Apache NetBeans Language Server with ${specifiedJDK ? specifiedJDK : 'default system JDK'}`;
log.appendLine(launchMsg);
vscode.window.setStatusBarMessage(launchMsg, 2000);
......@@ -192,6 +251,7 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
let ideRunning = new Promise((resolve, reject) => {
let collectedText : string | null = '';
function logAndWaitForEnabled(text: string) {
activationPending = false;
log.append(text);
if (collectedText == null) {
return;
......@@ -203,6 +263,7 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
}
}
let p = launcher.launch(info, "--modules", "--list");
console.log("LSP server launching: " + p.pid);
p.stdout.on('data', function(d: any) {
logAndWaitForEnabled(d.toString());
});
......@@ -210,8 +271,8 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
logAndWaitForEnabled(d.toString());
});
nbProcess = p;
nbProcess.on('close', function(code: number) {
if (p == nbProcess && code != 0) {
p.on('close', function(code: number) {
if (p == nbProcess && code != 0 && code) {
vscode.window.showWarningMessage("Apache NetBeans Language Server exited with " + code);
}
if (collectedText != null) {
......@@ -222,8 +283,10 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
log.appendLine("Cannot find org.netbeans.modules.java.lsp.server in the log!");
}
log.show(false);
killNbProcess(false, log, p);
reject("Apache NetBeans Language Server not enabled!");
} else {
console.log("LSP server " + p.pid + " terminated with " + code);
log.appendLine("Exit code " + code);
}
});
......@@ -293,7 +356,8 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
return ErrorAction.Continue;
},
closed : function(): CloseAction {
activateWithJDK(specifiedJDK, context, log, false);
log.appendLine("Connection to Apache NetBeans Language Server closed.");
restartWithJDKLater(10000, false);
return CloseAction.DoNotRestart;
}
}
......@@ -314,6 +378,7 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
client.onNotification(StatusMessageRequest.type, showStatusBarMessage);
});
}).catch((reason) => {
activationPending = false;
log.append(reason);
window.showErrorMessage('Error initializing ' + reason);
});
......@@ -351,14 +416,32 @@ function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext,
const yes = "Install GPLv2+CPEx code";
window.showErrorMessage("Additional Java Support is needed", yes).then(reply => {
if (yes === reply) {
let installProcess = launcher.launch(info, "--modules", "--install", ".*nbjavac.*");
let logData = function(d: any) {
log.append(d.toString());
vscode.window.setStatusBarMessage("Preparing Apache NetBeans Language Server for additional installation", 2000);
restartWithJDKLater = function() {
console.log("Ignoring request for restart of Apache NetBeans Language Server");
};
installProcess.stdout.on('data', logData);
installProcess.stderr.on('data', logData);
installProcess.on('close', function(code: number) {
log.append("Additional Java Support installed with exit code " + code);
maintenance = new Promise((resolve, reject) => {
const kill : Promise<void> = killNbProcess(false, log);
kill.then(() => {
let installProcess = launcher.launch(info, "-J-Dnetbeans.close=true", "--modules", "--install", ".*nbjavac.*");
console.log("Launching installation process: " + installProcess.pid);
let logData = function(d: any) {
log.append(d.toString());
};
installProcess.stdout.on('data', logData);
installProcess.stderr.on('data', logData);
installProcess.addListener("error", reject);
// MUST wait on 'close', since stdout is inherited by children. The installProcess dies but
// the inherited stream will be closed by the last child dying.
installProcess.on('close', function(code: number) {
console.log("Installation completed: " + installProcess.pid);
log.appendLine("Additional Java Support installed with exit code " + code);
// will be actually run after maintenance is resolve()d.
activateWithJDK(specifiedJDK, context, log, notifyKill)
resolve();
});
return installProcess;
});
});
}
});
......
......@@ -14,4 +14,5 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
3289B87AB9345958E16F3285ED884F5C4DAB7C2D platform-launchers-10.0.zip
0879C497EA45DF57BFF833A45AE9217FA447C713 platform-launchers-12.2.zip
Name: NetBeans Application Launchers
Description: Windows Launchers for the NetBeans Platform
Version: 10.0
Version: 12.2
License: Apache-2.0
Source: https://netbeans.org/
Origin: NetBeans
......
......@@ -433,6 +433,11 @@ while [ "$restart" ] ; do
trap '' EXIT
look_for_new_clusters
# If we should update anything, do it and restart IDE.
if [ $exitcode -eq 4 ] ; then
# Just connected to CLI, not in charge of running Updater or whatever.
exitcode=0
break
fi
run_updater=""
look_for_post_runs "$plathome"
......@@ -454,6 +459,5 @@ while [ "$restart" ] ; do
fi
done
# and we exit.
exit $exitcode
......@@ -43,9 +43,16 @@ extern "C" BOOL APIENTRY DllMain(HANDLE hModule,
return TRUE;
}
volatile int exitStatus = 0;
void exitHook(int status) {
exitStatus = status;
logMsg("Exit hook called with status %d", status);
launcher.onExit();
// do not handle possible restarts, if we are just CLI-connecting to a running process.
if (status != -252) {
launcher.onExit();
}
logMsg("Exit hook terminated.");
}
#define NBEXEC_EXPORT extern "C" __declspec(dllexport)
......
......@@ -24,6 +24,8 @@
#include "platformlauncher.h"
#include "argnames.h"
volatile extern int exitStatus;
using namespace std;
const char *PlatformLauncher::HELP_MSG =
......@@ -662,6 +664,10 @@ bool PlatformLauncher::restartRequested() {
void PlatformLauncher::onExit() {
logMsg("onExit()");
if (exitStatus == -252) {
logMsg("Exiting from CLI client, will not restart.");
return;
}
if (exiting) {
logMsg("Already exiting, no need to schedule restart");
......@@ -714,7 +720,8 @@ void PlatformLauncher::onExit() {
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(STARTUPINFO);
if (!CreateProcess(NULL, cmdLineStr, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
if (!CreateProcess(NULL, cmdLineStr, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
logErr(true, true, "Failed to create process.");
return;
}
......
......@@ -20,10 +20,10 @@ javac.source=1.8
module.jar.dir=lib
module.jar.basename=boot.jar
release.launcher/unix/nbexec=lib/nbexec
release.external/platform-launchers-10.0.zip!/nbexec.exe=lib/nbexec.exe
release.external/platform-launchers-10.0.zip!/nbexec64.exe=lib/nbexec64.exe
release.external/platform-launchers-10.0.zip!/nbexec.dll=lib/nbexec.dll
release.external/platform-launchers-10.0.zip!/nbexec64.dll=lib/nbexec64.dll
release.external/platform-launchers-12.2.zip!/nbexec.exe=lib/nbexec.exe
release.external/platform-launchers-12.2.zip!/nbexec64.exe=lib/nbexec64.exe
release.external/platform-launchers-12.2.zip!/nbexec.dll=lib/nbexec.dll
release.external/platform-launchers-12.2.zip!/nbexec64.dll=lib/nbexec64.dll
nbm.executable.files=lib/nbexec
javadoc.arch=${basedir}/arch.xml
......
......@@ -258,6 +258,7 @@ public abstract class CLIHandler extends Object {
public static final int CANNOT_CONNECT = -255;
public static final int CANNOT_WRITE = -254;
public static final int ALREADY_RUNNING = -253;
public static final int CONNECTED = -252;
private final File lockFile;
private final int port;
......
......@@ -60,7 +60,7 @@ final class MainImpl extends Object {
int res = execute (args, System.in, System.out, System.err, m);
if (res == -1) {
// Connected to another running NB instance and succeeded in making a call.
return;
System.exit(CLIHandler.Status.CONNECTED);
} else if (res != 0) {
// Some CLIHandler refused the invocation
if (res == Integer.MIN_VALUE) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment