Skip to content

Commit f6d6cad

Browse files
committed
Fix session collision issues when using multiple VS Code windows
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.
1 parent 2b97f5a commit f6d6cad

File tree

6 files changed

+66
-54
lines changed

6 files changed

+66
-54
lines changed

src/debugAdapter.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ var debugAdapterLogWriter =
2626
// debug server
2727
process.stdin.pause();
2828

29+
var debugSessionFilePath = utils.getDebugSessionFilePath();
30+
debugAdapterLogWriter.write("Session file path: " + debugSessionFilePath + ", pid: " + process.pid + " \r\n");
31+
2932
function startDebugging() {
3033
// Read the details of the current session to learn
3134
// the connection details for the debug service
32-
let sessionDetails = utils.readSessionFile();
35+
let sessionDetails = utils.readSessionFile(debugSessionFilePath);
36+
37+
// Delete the session file after it has been read so that
38+
// it isn't used mistakenly by another debug session
39+
utils.deleteSessionFile(debugSessionFilePath);
3340

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

97-
var sessionFilePath = utils.getSessionFilePath();
98104
function waitForSessionFile(triesRemaining: number) {
99105

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

102108
if (triesRemaining > 0) {
103-
if (utils.checkIfFileExists(sessionFilePath)) {
109+
if (utils.checkIfFileExists(debugSessionFilePath)) {
104110
debugAdapterLogWriter.write(`Session file present, connecting to debug adapter...\r\n\r\n`);
105111
startDebugging();
106112
}

src/features/DebugSession.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
*--------------------------------------------------------*/
44

55
import vscode = require('vscode');
6+
import utils = require('../utils');
67
import { IFeature } from '../feature';
8+
import { SessionManager } from '../session';
79
import { LanguageClient, RequestType, NotificationType } from 'vscode-languageclient';
810

911
export class DebugSessionFeature implements IFeature {
1012
private command: vscode.Disposable;
1113
private examplesPath: string;
1214

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

107+
// Write out temporary debug session file
108+
utils.writeSessionFile(
109+
utils.getDebugSessionFilePath(),
110+
this.sessionManager.getSessionDetails());
111+
105112
vscode.commands.executeCommand('vscode.startDebug', config);
106113
}
107114
}

src/logging.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class Logger {
5252
this.writeLine(message)
5353

5454
additionalMessages.forEach((line) => {
55-
this.writeLine(message);
55+
this.writeLine(line);
5656
});
5757
}
5858
}

src/main.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ var logger: Logger = undefined;
3636
var sessionManager: SessionManager = undefined;
3737
var extensionFeatures: IFeature[] = [];
3838

39-
// Clean up the session file just in case one lingers from a previous session
40-
utils.deleteSessionFile();
41-
4239
export function activate(context: vscode.ExtensionContext): void {
4340

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

99+
sessionManager =
100+
new SessionManager(
101+
requiredEditorServicesVersion,
102+
logger);
103+
102104
// Create features
103105
extensionFeatures = [
104106
new ConsoleFeature(),
@@ -113,16 +115,12 @@ export function activate(context: vscode.ExtensionContext): void {
113115
new NewFileOrProjectFeature(),
114116
new DocumentFormatterFeature(),
115117
new RemoteFilesFeature(),
116-
new DebugSessionFeature(),
118+
new DebugSessionFeature(sessionManager),
117119
new PickPSHostProcessFeature(),
118120
new SpecifyScriptArgsFeature(context)
119121
];
120122

121-
sessionManager =
122-
new SessionManager(
123-
requiredEditorServicesVersion,
124-
logger,
125-
extensionFeatures);
123+
sessionManager.setExtensionFeatures(extensionFeatures);
126124

127125
var extensionSettings = Settings.load(utils.PowerShellLanguageId);
128126
if (extensionSettings.startAutomatically) {

src/session.ts

+29-12
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,18 @@ export class SessionManager {
6363

6464
private hostVersion: string;
6565
private isWindowsOS: boolean;
66+
private sessionFilePath: string;
6667
private sessionStatus: SessionStatus;
6768
private focusConsoleOnExecute: boolean;
69+
private extensionFeatures: IFeature[] = [];
6870
private statusBarItem: vscode.StatusBarItem;
6971
private sessionConfiguration: SessionConfiguration;
7072
private versionDetails: PowerShellVersionDetails;
7173
private registeredCommands: vscode.Disposable[] = [];
7274
private consoleTerminal: vscode.Terminal = undefined;
7375
private languageServerClient: LanguageClient = undefined;
7476
private sessionSettings: Settings.ISettings = undefined;
77+
private sessionDetails: utils.EditorServicesSessionDetails;
7578

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

8285
constructor(
8386
private requiredEditorServicesVersion: string,
84-
private log: Logger,
85-
private extensionFeatures: IFeature[] = []) {
87+
private log: Logger) {
8688

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

@@ -107,6 +109,10 @@ export class SessionManager {
107109
this.registerCommands();
108110
}
109111

112+
public setExtensionFeatures(extensionFeatures: IFeature[]) {
113+
this.extensionFeatures = extensionFeatures;
114+
}
115+
110116
public start(sessionConfig: SessionConfiguration = { type: SessionType.UseDefault }) {
111117
this.sessionSettings = Settings.load(utils.PowerShellLanguageId);
112118
this.log.startNewLog(this.sessionSettings.developer.editorServicesLogLevel);
@@ -115,6 +121,10 @@ export class SessionManager {
115121

116122
this.createStatusBarItem();
117123

124+
this.sessionFilePath =
125+
utils.getSessionFilePath(
126+
Math.floor(100000 + Math.random() * 900000));
127+
118128
this.sessionConfiguration = this.resolveSessionConfiguration(sessionConfig);
119129

120130
if (this.sessionConfiguration.type === SessionType.UsePath ||
@@ -191,7 +201,7 @@ export class SessionManager {
191201
}
192202

193203
// Clean up the session file
194-
utils.deleteSessionFile();
204+
utils.deleteSessionFile(this.sessionFilePath);
195205

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

216+
public getSessionDetails(): utils.EditorServicesSessionDetails {
217+
return this.sessionDetails;
218+
}
219+
206220
public dispose() : void {
207221
// Stop the current session
208222
this.stop();
@@ -284,7 +298,7 @@ export class SessionManager {
284298

285299
startArgs +=
286300
`-LogPath '${editorServicesLogPath}' ` +
287-
`-SessionDetailsPath '${utils.getSessionFilePath()}' ` +
301+
`-SessionDetailsPath '${this.sessionFilePath}' ` +
288302
`-FeatureFlags @(${featureFlags})`
289303

290304
var powerShellArgs = [
@@ -316,11 +330,11 @@ export class SessionManager {
316330
powerShellExePath = batScriptPath;
317331
}
318332

319-
// Make sure no old session file exists
320-
utils.deleteSessionFile();
321-
322333
this.log.write(`${utils.getTimestampString()} Language server starting...`);
323334

335+
// Make sure no old session file exists
336+
utils.deleteSessionFile(this.sessionFilePath);
337+
324338
// Launch PowerShell in the integrated terminal
325339
this.consoleTerminal =
326340
vscode.window.createTerminal(
@@ -334,13 +348,16 @@ export class SessionManager {
334348

335349
// Start the language client
336350
utils.waitForSessionFile(
351+
this.sessionFilePath,
337352
(sessionDetails, error) => {
353+
this.sessionDetails = sessionDetails;
354+
338355
if (sessionDetails) {
339356
if (sessionDetails.status === "started") {
340357
this.log.write(`${utils.getTimestampString()} Language server started.`);
341358

342-
// Write out the session configuration file
343-
utils.writeSessionFile(sessionDetails);
359+
// The session file is no longer needed
360+
utils.deleteSessionFile(this.sessionFilePath);
344361

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

423440
var port = sessionDetails.languageServicePort;
424441

442+
// Log the session details object
443+
this.log.write(JSON.stringify(sessionDetails));
444+
425445
try
426446
{
427447
this.log.write("Connecting to language service on port " + port + "..." + os.EOL);
@@ -433,9 +453,6 @@ export class SessionManager {
433453
socket.on(
434454
'connect',
435455
() => {
436-
// Write out the session configuration file
437-
utils.writeSessionFile(sessionDetails);
438-
439456
this.log.write("Language service connected.");
440457
resolve({writer: socket, reader: socket})
441458
});

src/utils.ts

+12-28
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,6 @@ export function ensurePathExists(targetPath: string) {
1818
}
1919
}
2020

21-
export function getUniqueSessionId() {
22-
// We need to uniquely identify the current VS Code session
23-
// using some string so that we get a reliable pipe server name
24-
// for both the language and debug servers.
25-
26-
if (os.platform() == "linux") {
27-
// Electron running on Linux uses an additional layer of
28-
// separation between parent and child processes which
29-
// prevents environment variables from being inherited
30-
// easily. This causes VSCODE_PID to not be available
31-
// (for now) so use a different variable to get a
32-
// unique session.
33-
return process.env.VSCODE_PID;
34-
}
35-
else {
36-
// VSCODE_PID is available on Windows and OSX
37-
return process.env.VSCODE_PID;
38-
}
39-
}
40-
4121
export function getPipePath(pipeName: string) {
4222
if (os.platform() == "win32") {
4323
return "\\\\.\\pipe\\" + pipeName;
@@ -73,24 +53,28 @@ export interface WaitForSessionFileCallback {
7353
}
7454

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

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

81-
export function getSessionFilePath() {
82-
return sessionFilePath;
61+
export function getSessionFilePath(uniqueId: number) {
62+
return `${sessionFilePathPrefix}-${uniqueId}`;
63+
}
64+
65+
export function getDebugSessionFilePath() {
66+
return `${sessionFilePathPrefix}-Debug`;
8367
}
8468

85-
export function writeSessionFile(sessionDetails: EditorServicesSessionDetails) {
69+
export function writeSessionFile(sessionFilePath: string, sessionDetails: EditorServicesSessionDetails) {
8670
ensurePathExists(sessionsFolder);
8771

8872
var writeStream = fs.createWriteStream(sessionFilePath);
8973
writeStream.write(JSON.stringify(sessionDetails));
9074
writeStream.close();
9175
}
9276

93-
export function waitForSessionFile(callback: WaitForSessionFileCallback) {
77+
export function waitForSessionFile(sessionFilePath: string, callback: WaitForSessionFileCallback) {
9478

9579
function innerTryFunc(remainingTries: number, delayMilliseconds: number) {
9680
if (remainingTries == 0) {
@@ -104,20 +88,20 @@ export function waitForSessionFile(callback: WaitForSessionFileCallback) {
10488
}
10589
else {
10690
// Session file was found, load and return it
107-
callback(readSessionFile(), undefined);
91+
callback(readSessionFile(sessionFilePath), undefined);
10892
}
10993
}
11094

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

115-
export function readSessionFile(): EditorServicesSessionDetails {
99+
export function readSessionFile(sessionFilePath: string): EditorServicesSessionDetails {
116100
let fileContents = fs.readFileSync(sessionFilePath, "utf-8");
117101
return JSON.parse(fileContents)
118102
}
119103

120-
export function deleteSessionFile() {
104+
export function deleteSessionFile(sessionFilePath: string) {
121105
try {
122106
fs.unlinkSync(sessionFilePath);
123107
}

0 commit comments

Comments
 (0)