Skip to content

Commit

Permalink
Fix session collision issues when using multiple VS Code windows
Browse files Browse the repository at this point in the history
This change fixes an issue where multiple VS Code windows would
inadvertently share the same language server and debug adapter ports,
causing one of the windows to send all of its output and behavior to the
other window.  The fix is to make session file management more unique
between each window in the same VS Code process.

Resolves PowerShell#626.
  • Loading branch information
daviwil committed May 10, 2017
1 parent 2b97f5a commit f6d6cad
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 54 deletions.
12 changes: 9 additions & 3 deletions src/debugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ var debugAdapterLogWriter =
// debug server
process.stdin.pause();

var debugSessionFilePath = utils.getDebugSessionFilePath();
debugAdapterLogWriter.write("Session file path: " + debugSessionFilePath + ", pid: " + process.pid + " \r\n");

function startDebugging() {
// Read the details of the current session to learn
// the connection details for the debug service
let sessionDetails = utils.readSessionFile();
let sessionDetails = utils.readSessionFile(debugSessionFilePath);

// Delete the session file after it has been read so that
// it isn't used mistakenly by another debug session
utils.deleteSessionFile(debugSessionFilePath);

// Establish connection before setting up the session
debugAdapterLogWriter.write("Connecting to port: " + sessionDetails.debugServicePort + "\r\n");
Expand Down Expand Up @@ -94,13 +101,12 @@ function startDebugging() {
)
}

var sessionFilePath = utils.getSessionFilePath();
function waitForSessionFile(triesRemaining: number) {

debugAdapterLogWriter.write(`Waiting for session file, tries remaining: ${triesRemaining}...\r\n`);

if (triesRemaining > 0) {
if (utils.checkIfFileExists(sessionFilePath)) {
if (utils.checkIfFileExists(debugSessionFilePath)) {
debugAdapterLogWriter.write(`Session file present, connecting to debug adapter...\r\n\r\n`);
startDebugging();
}
Expand Down
9 changes: 8 additions & 1 deletion src/features/DebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
*--------------------------------------------------------*/

import vscode = require('vscode');
import utils = require('../utils');
import { IFeature } from '../feature';
import { SessionManager } from '../session';
import { LanguageClient, RequestType, NotificationType } from 'vscode-languageclient';

export class DebugSessionFeature implements IFeature {
private command: vscode.Disposable;
private examplesPath: string;

constructor() {
constructor(private sessionManager: SessionManager) {
this.command = vscode.commands.registerCommand(
'PowerShell.StartDebugSession',
config => { this.startDebugSession(config); });
Expand Down Expand Up @@ -102,6 +104,11 @@ export class DebugSessionFeature implements IFeature {
// TODO #367: Check if "newSession" mode is configured
vscode.commands.executeCommand('PowerShell.ShowSessionConsole', true);

// Write out temporary debug session file
utils.writeSessionFile(
utils.getDebugSessionFilePath(),
this.sessionManager.getSessionDetails());

vscode.commands.executeCommand('vscode.startDebug', config);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class Logger {
this.writeLine(message)

additionalMessages.forEach((line) => {
this.writeLine(message);
this.writeLine(line);
});
}
}
Expand Down
16 changes: 7 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ var logger: Logger = undefined;
var sessionManager: SessionManager = undefined;
var extensionFeatures: IFeature[] = [];

// Clean up the session file just in case one lingers from a previous session
utils.deleteSessionFile();

export function activate(context: vscode.ExtensionContext): void {

checkForUpdatedVersion(context);
Expand Down Expand Up @@ -99,6 +96,11 @@ export function activate(context: vscode.ExtensionContext): void {
// Create the logger
logger = new Logger();

sessionManager =
new SessionManager(
requiredEditorServicesVersion,
logger);

// Create features
extensionFeatures = [
new ConsoleFeature(),
Expand All @@ -113,16 +115,12 @@ export function activate(context: vscode.ExtensionContext): void {
new NewFileOrProjectFeature(),
new DocumentFormatterFeature(),
new RemoteFilesFeature(),
new DebugSessionFeature(),
new DebugSessionFeature(sessionManager),
new PickPSHostProcessFeature(),
new SpecifyScriptArgsFeature(context)
];

sessionManager =
new SessionManager(
requiredEditorServicesVersion,
logger,
extensionFeatures);
sessionManager.setExtensionFeatures(extensionFeatures);

var extensionSettings = Settings.load(utils.PowerShellLanguageId);
if (extensionSettings.startAutomatically) {
Expand Down
41 changes: 29 additions & 12 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,18 @@ export class SessionManager {

private hostVersion: string;
private isWindowsOS: boolean;
private sessionFilePath: string;
private sessionStatus: SessionStatus;
private focusConsoleOnExecute: boolean;
private extensionFeatures: IFeature[] = [];
private statusBarItem: vscode.StatusBarItem;
private sessionConfiguration: SessionConfiguration;
private versionDetails: PowerShellVersionDetails;
private registeredCommands: vscode.Disposable[] = [];
private consoleTerminal: vscode.Terminal = undefined;
private languageServerClient: LanguageClient = undefined;
private sessionSettings: Settings.ISettings = undefined;
private sessionDetails: utils.EditorServicesSessionDetails;

// When in development mode, VS Code's session ID is a fake
// value of "someValue.machineId". Use that to detect dev
Expand All @@ -81,8 +84,7 @@ export class SessionManager {

constructor(
private requiredEditorServicesVersion: string,
private log: Logger,
private extensionFeatures: IFeature[] = []) {
private log: Logger) {

this.isWindowsOS = os.platform() == "win32";

Expand All @@ -107,6 +109,10 @@ export class SessionManager {
this.registerCommands();
}

public setExtensionFeatures(extensionFeatures: IFeature[]) {
this.extensionFeatures = extensionFeatures;
}

public start(sessionConfig: SessionConfiguration = { type: SessionType.UseDefault }) {
this.sessionSettings = Settings.load(utils.PowerShellLanguageId);
this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel);
Expand All @@ -115,6 +121,10 @@ export class SessionManager {

this.createStatusBarItem();

this.sessionFilePath =
utils.getSessionFilePath(
Math.floor(100000 + Math.random() * 900000));

this.sessionConfiguration = this.resolveSessionConfiguration(sessionConfig);

if (this.sessionConfiguration.type === SessionType.UsePath ||
Expand Down Expand Up @@ -191,7 +201,7 @@ export class SessionManager {
}

// Clean up the session file
utils.deleteSessionFile();
utils.deleteSessionFile(this.sessionFilePath);

// Kill the PowerShell process we spawned via the console
if (this.consoleTerminal !== undefined) {
Expand All @@ -203,6 +213,10 @@ export class SessionManager {
this.sessionStatus = SessionStatus.NotStarted;
}

public getSessionDetails(): utils.EditorServicesSessionDetails {
return this.sessionDetails;
}

public dispose() : void {
// Stop the current session
this.stop();
Expand Down Expand Up @@ -284,7 +298,7 @@ export class SessionManager {

startArgs +=
`-LogPath '${editorServicesLogPath}' ` +
`-SessionDetailsPath '${utils.getSessionFilePath()}' ` +
`-SessionDetailsPath '${this.sessionFilePath}' ` +
`-FeatureFlags @(${featureFlags})`

var powerShellArgs = [
Expand Down Expand Up @@ -316,11 +330,11 @@ export class SessionManager {
powerShellExePath = batScriptPath;
}

// Make sure no old session file exists
utils.deleteSessionFile();

this.log.write(`${utils.getTimestampString()} Language server starting...`);

// Make sure no old session file exists
utils.deleteSessionFile(this.sessionFilePath);

// Launch PowerShell in the integrated terminal
this.consoleTerminal =
vscode.window.createTerminal(
Expand All @@ -334,13 +348,16 @@ export class SessionManager {

// Start the language client
utils.waitForSessionFile(
this.sessionFilePath,
(sessionDetails, error) => {
this.sessionDetails = sessionDetails;

if (sessionDetails) {
if (sessionDetails.status === "started") {
this.log.write(`${utils.getTimestampString()} Language server started.`);

// Write out the session configuration file
utils.writeSessionFile(sessionDetails);
// The session file is no longer needed
utils.deleteSessionFile(this.sessionFilePath);

// Start the language service client
this.startLanguageClient(sessionDetails);
Expand Down Expand Up @@ -422,6 +439,9 @@ export class SessionManager {

var port = sessionDetails.languageServicePort;

// Log the session details object
this.log.write(JSON.stringify(sessionDetails));

try
{
this.log.write("Connecting to language service on port " + port + "..." + os.EOL);
Expand All @@ -433,9 +453,6 @@ export class SessionManager {
socket.on(
'connect',
() => {
// Write out the session configuration file
utils.writeSessionFile(sessionDetails);

this.log.write("Language service connected.");
resolve({writer: socket, reader: socket})
});
Expand Down
40 changes: 12 additions & 28 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,6 @@ export function ensurePathExists(targetPath: string) {
}
}

export function getUniqueSessionId() {
// We need to uniquely identify the current VS Code session
// using some string so that we get a reliable pipe server name
// for both the language and debug servers.

if (os.platform() == "linux") {
// Electron running on Linux uses an additional layer of
// separation between parent and child processes which
// prevents environment variables from being inherited
// easily. This causes VSCODE_PID to not be available
// (for now) so use a different variable to get a
// unique session.
return process.env.VSCODE_PID;
}
else {
// VSCODE_PID is available on Windows and OSX
return process.env.VSCODE_PID;
}
}

export function getPipePath(pipeName: string) {
if (os.platform() == "win32") {
return "\\\\.\\pipe\\" + pipeName;
Expand Down Expand Up @@ -73,24 +53,28 @@ export interface WaitForSessionFileCallback {
}

let sessionsFolder = path.resolve(__dirname, "..", "sessions/");
let sessionFilePath = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID);
let sessionFilePathPrefix = path.resolve(sessionsFolder, "PSES-VSCode-" + process.env.VSCODE_PID);

// Create the sessions path if it doesn't exist already
ensurePathExists(sessionsFolder);

export function getSessionFilePath() {
return sessionFilePath;
export function getSessionFilePath(uniqueId: number) {
return `${sessionFilePathPrefix}-${uniqueId}`;
}

export function getDebugSessionFilePath() {
return `${sessionFilePathPrefix}-Debug`;
}

export function writeSessionFile(sessionDetails: EditorServicesSessionDetails) {
export function writeSessionFile(sessionFilePath: string, sessionDetails: EditorServicesSessionDetails) {
ensurePathExists(sessionsFolder);

var writeStream = fs.createWriteStream(sessionFilePath);
writeStream.write(JSON.stringify(sessionDetails));
writeStream.close();
}

export function waitForSessionFile(callback: WaitForSessionFileCallback) {
export function waitForSessionFile(sessionFilePath: string, callback: WaitForSessionFileCallback) {

function innerTryFunc(remainingTries: number, delayMilliseconds: number) {
if (remainingTries == 0) {
Expand All @@ -104,20 +88,20 @@ export function waitForSessionFile(callback: WaitForSessionFileCallback) {
}
else {
// Session file was found, load and return it
callback(readSessionFile(), undefined);
callback(readSessionFile(sessionFilePath), undefined);
}
}

// Try once per second for 60 seconds, one full minute
innerTryFunc(60, 1000);
}

export function readSessionFile(): EditorServicesSessionDetails {
export function readSessionFile(sessionFilePath: string): EditorServicesSessionDetails {
let fileContents = fs.readFileSync(sessionFilePath, "utf-8");
return JSON.parse(fileContents)
}

export function deleteSessionFile() {
export function deleteSessionFile(sessionFilePath: string) {
try {
fs.unlinkSync(sessionFilePath);
}
Expand Down

0 comments on commit f6d6cad

Please sign in to comment.