Skip to content
This repository was archived by the owner on Oct 2, 2021. It is now read-only.

Fix possible race condition for new and changed scripts #336

Merged
merged 2 commits into from
Jun 21, 2018
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
9 changes: 3 additions & 6 deletions src/chrome/chromeDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,8 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter {
}

protected async sendLoadedSourceEvent(script: Crdp.Debugger.ScriptParsedEvent, loadedSourceEventReason: LoadedSourceEventReason = 'new'): Promise<void> {
const source = await this.scriptToSource(script);

// This is a workaround for an edge bug, see https://github.com/Microsoft/vscode-chrome-debug-core/pull/329
switch (loadedSourceEventReason) {
case 'new':
Expand All @@ -878,7 +880,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter {
telemetry.reportEvent('LoadedSourceEventError', { issue: 'Unknown reason', reason: loadedSourceEventReason });
}

const scriptEvent = await this.scriptToLoadedSourceEvent(loadedSourceEventReason, script);
const scriptEvent = new LoadedSourceEvent(loadedSourceEventReason, source as any);

this._session.sendEvent(scriptEvent);
}
Expand Down Expand Up @@ -1944,11 +1946,6 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter {
};
}

private async scriptToLoadedSourceEvent(reason: LoadedSourceEventReason, script: Crdp.Debugger.ScriptParsedEvent): Promise<LoadedSourceEvent> {
const source = await this.scriptToSource(script);
return new LoadedSourceEvent(reason, source as any);
}

private async scriptToSource(script: Crdp.Debugger.ScriptParsedEvent): Promise<DebugProtocol.Source> {
const sourceReference = this.getSourceReferenceForScriptId(script.scriptId);
const origin = this.getReadonlyOrigin(script.url);
Expand Down
73 changes: 73 additions & 0 deletions test/chrome/chromeDebugAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Protocol as Crdp } from 'devtools-protocol';

import * as testUtils from '../testUtils';
import * as utils from '../../src/utils';
import * as fs from 'fs';

/** Not mocked - use for type only */
import {ChromeDebugAdapter as _ChromeDebugAdapter } from '../../src/chrome/chromeDebugAdapter';
Expand Down Expand Up @@ -603,6 +604,78 @@ suite('ChromeDebugAdapter', () => {
});
});

// This is needed for Edge debug adapter, please keep the logic of sendLoadedSourceEvent()
test('tests that sendLoadedSourceEvent will set the `reason` parameter based on our internal view of the events we sent to the client even if fs.access takes unexpected times while blocking async', async () => {
let eventIndex = 0;
sendEventHandler = (event) => {
switch (eventIndex) {
case 0:
assert.equal('loadedSource', event.event);
assert.notEqual(null, event.body);
assert.equal('new', event.body.reason);
break;
case 1:
assert.equal('loadedSource', event.event);
assert.notEqual(null, event.body);
assert.equal('changed', event.body.reason);
break;
default:
throw new RangeError('Unexpected event index');
}
++eventIndex;
};

await chromeDebugAdapter.attach(ATTACH_ARGS);

const originalFSAccess = fs.access;
let callIndex = 0;
let callbackForFirstEvent = null;

/* Mock fs.access so the first call will block until the second call is finished */
(fs as any).access = (path, callback) => {
if (callIndex === 0) {
callbackForFirstEvent = callback;
console.log("Blocking first fs.access until second call is finished");
++callIndex;
} else {
callback();

if (callbackForFirstEvent !== null) {
console.log("Second call when thorugh. Unblocking first call");
setTimeout(callbackForFirstEvent, 50);
callbackForFirstEvent = null;
}
}
};

try {
const firstEvent = (<any>chromeDebugAdapter).sendLoadedSourceEvent({
scriptId: 1,
url: '',
startLine: 0,
startColumn: 0,
endLine: 0,
endColumn: 0,
executionContextId: 0,
hash: ''
});
const secondEvent = (<any>chromeDebugAdapter).sendLoadedSourceEvent({
scriptId: 1,
url: '',
startLine: 0,
startColumn: 0,
endLine: 0,
endColumn: 0,
executionContextId: 0,
hash: ''
});

await Promise.all([firstEvent, secondEvent]);
} finally {
(fs as any).access = originalFSAccess;
}
});

function createSource(name: string, path?: string, sourceReference?: number, origin?: string): Source {
return <Source>{
name: name,
Expand Down