Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Reinitiate tooling service on Node process crash #14724

Merged
merged 6 commits into from
Apr 25, 2019
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
4 changes: 4 additions & 0 deletions src/extensions/default/PhpTooling/CodeHintsProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ define(function (require, exports, module) {
this.defaultCodeHintProviders = new DefaultProviders.CodeHintsProvider(client);
}

CodeHintsProvider.prototype.setClient = function (client) {
this.defaultCodeHintProviders.setClient(client);
};

function setStyleAndCacheToken($hintObj, token) {
$hintObj.addClass('brackets-hints-with-type-details');
$hintObj.data('completionItem', token);
Expand Down
12 changes: 11 additions & 1 deletion src/extensions/default/PhpTooling/PHPSymbolProviders.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*/

/*jslint regexp: true */

/*eslint no-invalid-this: 0, max-len: 0*/
define(function (require, exports, module) {
"use strict";

Expand All @@ -34,6 +34,12 @@ define(function (require, exports, module) {

var SymbolKind = QuickOpen.SymbolKind;

function setClient(client) {
if (client) {
this.client = client;
}
}

function convertRangePosToEditorPos(rangePos) {
return {
line: rangePos.line,
Expand Down Expand Up @@ -112,6 +118,8 @@ define(function (require, exports, module) {
this.client = client;
}

DocumentSymbolsProvider.prototype.setClient = setClient;

DocumentSymbolsProvider.prototype.match = function (query) {
return query.startsWith("@");
};
Expand Down Expand Up @@ -171,6 +179,8 @@ define(function (require, exports, module) {
this.client = client;
}

ProjectSymbolsProvider.prototype.setClient = setClient;

ProjectSymbolsProvider.prototype.match = function (query) {
return query.startsWith("#");
};
Expand Down
56 changes: 47 additions & 9 deletions src/extensions/default/PhpTooling/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ define(function (require, exports, module) {
"use strict";

var LanguageTools = brackets.getModule("languageTools/LanguageTools"),
ClientLoader = brackets.getModule("languageTools/ClientLoader"),
AppInit = brackets.getModule("utils/AppInit"),
ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
ProjectManager = brackets.getModule("project/ProjectManager"),
Expand Down Expand Up @@ -61,13 +62,14 @@ define(function (require, exports, module) {
phpServerRunning = false,
serverCapabilities,
currentRootPath,
chProvider,
phProvider,
lProvider,
jdProvider,
dSymProvider,
pSymProvider,
refProvider;
chProvider = null,
phProvider = null,
lProvider = null,
jdProvider = null,
dSymProvider = null,
pSymProvider = null,
refProvider = null,
providersRegistered = false;

PreferencesManager.definePreference("php", "object", phpConfig, {
description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION
Expand Down Expand Up @@ -103,6 +105,18 @@ define(function (require, exports, module) {
}
};

function resetClientInProviders() {
var logErr = "PhpTooling: Can't reset client for : ";
chProvider ? chProvider.setClient(_client) : console.log(logErr, "CodeHintsProvider");
phProvider ? phProvider.setClient(_client) : console.log(logErr, "ParameterHintsProvider");
jdProvider ? jdProvider.setClient(_client) : console.log(logErr, "JumpToDefProvider");
dSymProvider ? dSymProvider.setClient(_client) : console.log(logErr, "DocumentSymbolsProvider");
pSymProvider ? pSymProvider.setClient(_client) : console.log(logErr, "ProjectSymbolsProvider");
refProvider ? refProvider.setClient(_client) : console.log(logErr, "FindReferencesProvider");
lProvider ? lProvider.setClient(_client) : console.log(logErr, "LintingProvider");
_client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider));
}

function registerToolingProviders() {
chProvider = new CodeHintsProvider(_client),
phProvider = new DefaultProviders.ParameterHintsProvider(_client),
Expand Down Expand Up @@ -147,6 +161,8 @@ define(function (require, exports, module) {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(true);

_client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider));

providersRegistered = true;
}

function addEventHandlers() {
Expand Down Expand Up @@ -214,7 +230,13 @@ define(function (require, exports, module) {
function handlePostPhpServerStart() {
if (!phpServerRunning) {
phpServerRunning = true;
registerToolingProviders();

if (providersRegistered) {
resetClientInProviders();
} else {
registerToolingProviders();
}

addEventHandlers();
EditorManager.off("activeEditorChange.php");
LanguageManager.off("languageModified.php");
Expand Down Expand Up @@ -262,13 +284,29 @@ define(function (require, exports, module) {
}
}

AppInit.appReady(function () {
function initiateService(evt, onAppReady) {
if (onAppReady) {
console.log("Php tooling: Starting the service");
} else {
console.log("Php tooling: Something went wrong. Restarting the service");
}

phpServerRunning = false;
LanguageTools.initiateToolingService(clientName, clientFilePath, ['php']).done(function (client) {
_client = client;
//Attach only once
EditorManager.off("activeEditorChange.php");
EditorManager.on("activeEditorChange.php", activeEditorChangeHandler);
//Attach only once
LanguageManager.off("languageModified.php");
LanguageManager.on("languageModified.php", languageModifiedHandler);
activeEditorChangeHandler(null, EditorManager.getActiveEditor());
});
}

AppInit.appReady(function () {
initiateService(null, true);
ClientLoader.on("languageClientModuleInitialized", initiateService);
});

//Only for Unit testing
Expand Down
77 changes: 64 additions & 13 deletions src/languageTools/ClientLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ define(function (require, exports, module) {
var ToolingInfo = JSON.parse(require("text!languageTools/ToolingInfo.json")),
NodeDomain = require("utils/NodeDomain"),
FileUtils = require("file/FileUtils"),
EventDispatcher = require("utils/EventDispatcher"),
BracketsToNodeInterface = require("languageTools/BracketsToNodeInterface").BracketsToNodeInterface;

EventDispatcher.makeEventDispatcher(exports);
//Register paths required for Language Client and also register default brackets capabilities.
var _bracketsPath = FileUtils.getNativeBracketsDirectoryPath();
// The native directory path ends with either "test" or "src".
Expand All @@ -39,22 +41,12 @@ define(function (require, exports, module) {
var _modulePath = FileUtils.getNativeModuleDirectoryPath(module),
_nodePath = "node/RegisterLanguageClientInfo",
_domainPath = [_bracketsPath, _modulePath, _nodePath].join("/"),
clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath),
//Init node with Information required by Language Client
clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo),
clientInfoDomain = null,
clientInfoLoadedPromise = null,
//Clients that have to be loaded once the LanguageClient info is successfully loaded on the
//node side.
pendingClientsToBeLoaded = [];

//Attach success and failure function for the clientInfoLoadedPromise
clientInfoLoadedPromise.then(function () {
pendingClientsToBeLoaded.forEach(function (pendingClient) {
pendingClient.load();
});
}, function () {
console.log("Failed to Initialize LanguageClient Module Information.");
});

function syncPrefsWithDomain(languageToolsPrefs) {
if (clientInfoDomain) {
clientInfoDomain.exec("syncPreferences", languageToolsPrefs);
Expand Down Expand Up @@ -111,7 +103,7 @@ define(function (require, exports, module) {
var result = $.Deferred();

//Only load clients after the LanguageClient Info has been initialized
if (clientInfoLoadedPromise.state() === "pending") {
if (!clientInfoLoadedPromise || clientInfoLoadedPromise.state() === "pending") {
var pendingClient = {
load: _clientLoader.bind(null, clientName, clientFilePath, result)
};
Expand All @@ -123,6 +115,65 @@ define(function (require, exports, module) {
return result;
}

/**
* This function passes Brackets's native directory path as well as the tooling commands
* required by the LanguageClient node module. This information is then maintained in memory
* in the node process server for succesfully loading and functioning of all language clients
* since it is a direct dependency.
*/
function sendLanguageClientInfo() {
//Init node with Information required by Language Client
clientInfoLoadedPromise = clientInfoDomain.exec("initialize", _bracketsPath, ToolingInfo);

function logInitializationError() {
console.error("Failed to Initialize LanguageClient Module Information.");
}

//Attach success and failure function for the clientInfoLoadedPromise
clientInfoLoadedPromise.then(function (success) {
if (!success) {
logInitializationError();
return;
}

if (Array.isArray(pendingClientsToBeLoaded)) {
pendingClientsToBeLoaded.forEach(function (pendingClient) {
pendingClient.load();
});
} else {
exports.trigger("languageClientModuleInitialized");
}
pendingClientsToBeLoaded = null;
}, function () {
logInitializationError();
});
}

/**
* This function starts a domain which initializes the LanguageClient node module
* required by the Language Server Protocol framework in Brackets. All the LSP clients
* can only be successfully initiated once this domain has been successfully loaded and
* the LanguageClient info initialized. Refer to sendLanguageClientInfo for more.
*/
function initDomainAndHandleNodeCrash() {
clientInfoDomain = new NodeDomain("LanguageClientInfo", _domainPath);
//Initialize LanguageClientInfo once the domain has successfully loaded.
clientInfoDomain.promise().done(function () {
sendLanguageClientInfo();
//This is to handle the node failure. If the node process dies, we get an on close
//event on the websocket connection object. Brackets then spawns another process and
//restablishes the connection. Once the connection is restablished we send reinitialize
//the LanguageClient info.
clientInfoDomain.connection.on("close", function (event, reconnectedPromise) {
reconnectedPromise.done(sendLanguageClientInfo);
});
}).fail(function (err) {
console.error("ClientInfo domain could not be loaded: ", err);
});
}
initDomainAndHandleNodeCrash();


exports.initiateLanguageClient = initiateLanguageClient;
exports.syncPrefsWithDomain = syncPrefsWithDomain;
});
18 changes: 17 additions & 1 deletion src/languageTools/DefaultProviders.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

/*global Map*/
/* eslint-disable indent */
/* eslint max-len: ["error", { "code": 200 }]*/
/* eslint max-len: ["error", { "code": 200 }], no-invalid-this: 0*/
define(function (require, exports, module) {
"use strict";

Expand All @@ -44,12 +44,20 @@ define(function (require, exports, module) {

ExtensionUtils.loadStyleSheet(module, "styles/default_provider_style.css");

function setClient(client) {
if (client) {
this.client = client;
}
}

function CodeHintsProvider(client) {
this.client = client;
this.query = "";
this.ignoreQuery = ["-", "->", ">", ":", "::", "(", "()", ")", "[", "[]", "]", "{", "{}", "}"];
}

CodeHintsProvider.prototype.setClient = setClient;

function formatTypeDataForToken($hintObj, token) {
$hintObj.addClass('brackets-hints-with-type-details');
if (token.detail) {
Expand Down Expand Up @@ -197,6 +205,8 @@ define(function (require, exports, module) {
this.client = client;
}

ParameterHintsProvider.prototype.setClient = setClient;

ParameterHintsProvider.prototype.hasParameterHints = function (editor, implicitChar) {
if (!this.client) {
return false;
Expand Down Expand Up @@ -273,6 +283,8 @@ define(function (require, exports, module) {
this.client = client;
}

JumpToDefProvider.prototype.setClient = setClient;

JumpToDefProvider.prototype.canJumpToDef = function (editor, implicitChar) {
if (!this.client) {
return false;
Expand Down Expand Up @@ -342,6 +354,8 @@ define(function (require, exports, module) {
this._validateOnType = false;
}

LintingProvider.prototype.setClient = setClient;

LintingProvider.prototype.clearExistingResults = function (filePath) {
var filePathProvided = !!filePath;

Expand Down Expand Up @@ -451,6 +465,8 @@ define(function (require, exports, module) {
this.client = client;
}

ReferencesProvider.prototype.setClient = setClient;

ReferencesProvider.prototype.hasReferences = function() {
if (!this.client) {
return false;
Expand Down
10 changes: 8 additions & 2 deletions src/languageTools/node/RegisterLanguageClientInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ function syncPreferences(prefs) {
global.LanguageClientInfo.preferences = prefs || global.LanguageClientInfo.preferences || {};
}

function initialize(bracketsSourcePath, toolingInfo) {
function initialize(bracketsSourcePath, toolingInfo, resolve) {
if (!bracketsSourcePath || !toolingInfo) {
resolve(true, null); //resolve with err param
}

var normalizedBracketsSourcePath = bracketsSourcePath.split(BACKWARD_SLASH).join(FORWARD_SLASH),
bracketsSourcePathArray = normalizedBracketsSourcePath.split(FORWARD_SLASH),
languageClientAbsolutePath = bracketsSourcePathArray.concat(LANGUAGE_CLIENT_RELATIVE_PATH_ARRAY).join(FORWARD_SLASH);
Expand All @@ -239,6 +243,8 @@ function initialize(bracketsSourcePath, toolingInfo) {
global.LanguageClientInfo.defaultBracketsCapabilities = defaultBracketsCapabilities;
global.LanguageClientInfo.toolingInfo = toolingInfo;
global.LanguageClientInfo.preferences = {};

resolve(null, true); //resolve with boolean denoting success
}

function init(domainManager) {
Expand All @@ -253,7 +259,7 @@ function init(domainManager) {
domainName,
"initialize",
initialize,
false,
true,
"Initialize node environment for Language Client Module",
[
{
Expand Down