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: Jaroslav Tulach <jaroslav.tulach@oracle.com>
diff --git a/java/java.lsp.server/nbcode/nbproject/genfiles.properties b/java/java.lsp.server/nbcode/nbproject/genfiles.properties
index d7a1e40..48caec6 100644
--- a/java/java.lsp.server/nbcode/nbproject/genfiles.properties
+++ b/java/java.lsp.server/nbcode/nbproject/genfiles.properties
@@ -3,9 +3,9 @@
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
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index 5b51355..963da4d 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -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": {
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 7a3df78..2981f72 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -168,13 +168,73 @@
}));
}
+/**
+ * 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 @@
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 @@
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 @@
}
}
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 @@
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 @@
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 @@
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 @@
client.onNotification(StatusMessageRequest.type, showStatusBarMessage);
});
}).catch((reason) => {
+ activationPending = false;
log.append(reason);
window.showErrorMessage('Error initializing ' + reason);
});
@@ -351,14 +416,32 @@
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;
+ });
});
}
});
diff --git a/platform/o.n.bootstrap/external/binaries-list b/platform/o.n.bootstrap/external/binaries-list
index 511ab38..99132f1 100644
--- a/platform/o.n.bootstrap/external/binaries-list
+++ b/platform/o.n.bootstrap/external/binaries-list
@@ -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
+
diff --git a/platform/o.n.bootstrap/external/platform-launchers-10.0-license.txt b/platform/o.n.bootstrap/external/platform-launchers-12.2-license.txt
similarity index 99%
rename from platform/o.n.bootstrap/external/platform-launchers-10.0-license.txt
rename to platform/o.n.bootstrap/external/platform-launchers-12.2-license.txt
index b57feb7..c2ad62b 100644
--- a/platform/o.n.bootstrap/external/platform-launchers-10.0-license.txt
+++ b/platform/o.n.bootstrap/external/platform-launchers-12.2-license.txt
@@ -1,6 +1,6 @@
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
diff --git a/platform/o.n.bootstrap/launcher/unix/nbexec b/platform/o.n.bootstrap/launcher/unix/nbexec
old mode 100644
new mode 100755
index ca7d16b..f59096a
--- a/platform/o.n.bootstrap/launcher/unix/nbexec
+++ b/platform/o.n.bootstrap/launcher/unix/nbexec
@@ -433,6 +433,11 @@
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 @@
fi
done
-
# and we exit.
exit $exitcode
diff --git a/platform/o.n.bootstrap/launcher/windows/nbexec.cpp b/platform/o.n.bootstrap/launcher/windows/nbexec.cpp
index 2e63d02..47e490e 100644
--- a/platform/o.n.bootstrap/launcher/windows/nbexec.cpp
+++ b/platform/o.n.bootstrap/launcher/windows/nbexec.cpp
@@ -43,9 +43,16 @@
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)
diff --git a/platform/o.n.bootstrap/launcher/windows/platformlauncher.cpp b/platform/o.n.bootstrap/launcher/windows/platformlauncher.cpp
index b29b309..8df954c 100644
--- a/platform/o.n.bootstrap/launcher/windows/platformlauncher.cpp
+++ b/platform/o.n.bootstrap/launcher/windows/platformlauncher.cpp
@@ -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 @@
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 @@
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;
}
diff --git a/platform/o.n.bootstrap/nbproject/project.properties b/platform/o.n.bootstrap/nbproject/project.properties
index e10ee5b..83ac777 100644
--- a/platform/o.n.bootstrap/nbproject/project.properties
+++ b/platform/o.n.bootstrap/nbproject/project.properties
@@ -20,10 +20,10 @@
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
diff --git a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
index ebd7f2e..5e2bf4a 100644
--- a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
+++ b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
@@ -258,6 +258,7 @@
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;
diff --git a/platform/o.n.bootstrap/src/org/netbeans/MainImpl.java b/platform/o.n.bootstrap/src/org/netbeans/MainImpl.java
index 577c392..b812f97 100644
--- a/platform/o.n.bootstrap/src/org/netbeans/MainImpl.java
+++ b/platform/o.n.bootstrap/src/org/netbeans/MainImpl.java
@@ -60,7 +60,7 @@
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) {