Change:
After upgrading to the 1.27.0-insider build from the 1.26.0-insider our Debug Adapter we're receiving the initializeRequest event and in that even we're sending a custom event to our extension, which has an event handler for onDidReceiveDebugSessionCustomEvent registered, but the event is never received. I tracked the sendEvent to the point where it gets written to the writableStream in and everything looks fine. On the extension side I was broken in and the only events that came in were onDidChangeWindowFocus events that were happening due to me swapping VS Code instances (I'm debugging the DA and Extension in one VS Code instance and the actual project in the other). After uninstalling 1.27.0-insider and re-installing 1.26.0-insider things were working just fine. Our debug adapter's initializeRequest method is here: Our extension's onDidReceiveDebugSessionCustomEvent handler is setup here: VSCode Version: 1.27.0-insider OS Version: Linux: Ubuntu 14.04 Architecture x64 and Mac OSX 10.13.6 (windows wasn't tried) Steps to Reproduce: I'm kind of hoping you guys recognize what's going on or can give me some tips on trying to diagnose this further as there's no good way to trim down the DA and the extension code for a small repro. I can also setup a goto meeting or hangout to walk someone through it. clone - pull down the repository, run npm install. In an VS Code instance (1.27.0-insider) open up the salesforcedx-vscode, select Launch Replay Debugger adapter which will build everything and launch the debugger adapter. launching the Replay Debugger adapter select Launch Extensions without compile, this will launch another instance of VS Code which we'll be loading the project. clone - Using the instance of VS Code opened up in step 2, open up the dreamhouse-sfdx directory. the project select add configuration and select Apex Replay Debugger: Launch. In that configuration add the "debugServer": 4712 after the "trace": true entry. This allows debugging of both the extension and debug adapter locally. Apex Replay Debugger - you'll get prompted to select a log file however any .txt will do since it'll hang before it even gets to the point where it does anything with. Putting breakpoints in and you can see the event get sent by the DA and never get it by the extension. The VS Code that launched the replay debugger will continue to spin and never start responding. It'll have to be killed. In order to start things again you're going to have to run kill -9 $(lsof -t -i :4712) because it's still listening on the port and run steps 2 and 5 again.
we are planning to fix this regression for the August release. It was introduced by the refactoring necessary for the EH debugging fix and it was not discovered in the test pass ().
OK to fix
Fix is low risk because it only fixes the lost early custom event (by introducing a new lifecycle event) and does not affect anything else.
thanks a lot for filling this and for using insiders.
yes, thanks a lot for testing your extension with Insiders and reporting any regressions you find. And the earlier you test and report, the better ;-)
and thank you all for looking at this. I use insiders whenever possible, especially while doing development with the exception being if there's something blocking. So far, this been one of the only issues I've hit in the past 8 months. Everyone working on VS Code has done a great job with the product and I've been more than happy with the quality of VS Code. I see this has been fixed, when will the new insiders build have the fix in it?
Does the fix work for you?
The current insiders build has the fix (update came out a couple of hours ago).
I just installed this version and it still hangs. Is this the version with the fix in? I was just using "sudo apt-get install code-insiders" to install (I normally run this 3-4x a week to get newer versions) Version: 1.27.0-insider Commit: Date: 2018-08-31T10:41:42.550Z Electron: 2.0.7 Chrome: 61.0.3163.100 8.9.3 V8: 6.1.534.41 Architecture: x64
yes, you are using the correct version. We could not get your debugger to work because we couldn't build it successfully. So we tried to reproduce your problem with our example debugger "mock-debug" by making these changes: So basically we register for custom events on activate and we send the custom event from the "initialize" request before returning the "initialize" response. These changes were based on the two source code links and the explanation that you had in your original comment. We could reproduce the problem in the Insider release from yesterday and our fix solved the problem for us. Please note that you were always living on the edge by sending custom events that early: For VS Code a debug session starts after it has received a successful "initialize" response. From then on it is possible to receive custom events. Your implementation seems to send custom events before the "initialize" response. You were lucky that this worked previously, and for a refactoring that we had to do, this code had been cleaned up and we were only accepting custom events after the a successful "initialize" response. Since we did not expect that any DA implementation would rely on that behaviour, we weren't aware that this change was a breaking change. For this reason we've decided to revert the new behaviour and accept custom events "early" again. If our change doesn't work for you, then there must be another timing-related thing that your code is relying on. One thing that looked suspicious to me is the fact that you delay the "initialize" response until you receive a customRequest from the custom event handler... I've just tried to add this to my mock-debug branch (see ) and this in fact shows the next problem: the custom request again comes too early and is ignored because the session has not yet been initialized. I wonder why you have to do this? Couldn't you just add the LINEBREAKPOINTINFO stuff as some private data to a launch config in your DebugConfigurationProvider.resolveDebugConfiguration method? Then that data will be available in your launchRequest or attachRequest method. We will not be able to provide a fix for this before next week. /cc
First, I'd like to acknowledge the effort you've put into assisting me with this. I thank you, it is very much appreciated. I'm probably still going to need some of your expertise to fix this on our end then. I'm not 100% sure what the correct ordering of events needs to be here. What's going on with our code is that we'd fail initialization if we weren't able to get the source/line information for breakpoints from our language server. This had seemed like the correct thing to do since we didn't want to start the session if we couldn't get the source/line breakpoint information. If initialization has to complete before any custom events can be sent then I guess the follow apply: 's the next, best, earliest place to fire that event? We really can't do anything unless the call to our language server is successful. the session is initialized and we figure out where to move the the breakpoint information call, what's the best way to un-initialize or stop the session? I didn't see your edit until after I'd submitted, you'd asked the following question: "I wonder why you have to do this? Couldn't you just add the LINEBREAKPOINTINFO stuff as some private data to a launch config in your DebugConfigurationProvider.resolveDebugConfiguration method? Then that data will be available in your launchRequest or attachRequest method." This is where things are "interesting". This particular DA/extension is for replay debugging, not live which alleviates any attach debugging. The code, itself, is actually run within our application server in the user's org at Salesforce. The result of which produces a log. The user can push source to or pull source from their organization, run it on the remote application server then pull down their debug logs to start a a replay-debug session. It's because of this that we were trying to get the source/line breakpoint information at time they're starting the session. Thanks, Jim
It's also worth asking, when is this update moving from code-insiders to code? I know you'd said next week but I was curious as to when? I've got a few things to try on my end and I'm sure with some guidance from I can get this fixed in the DA/extension interaction.
I have a fix for the "early customRequest" issue and I will try to get this into into VS Code on Monday. So it will make it into VS Code 1.27 which is due mid of next week. I will prepare an Insiders build and it would be great if you could verify that with your extension. Is a macOS build ok? I'll let you know the details as soon as I have the build. But I still think that you should try a different approach to solve your issue of making language server data available to the DA. IMO the simplest approach would be to add the LINEBREAKPOINTINFO to the launch config so that it arrives here: This is still before you send the InitializedEvent: which is by definition before VS Code sends any persisted breakpoints. The code for this look would live in a method of your DebugConfigurationProvider and it just uses the code to retrieve the data from the language server and adds it as a property "__privateData" to the launch config which is then passed to the launch request: In case of errors you could show a nice error dialog and then abort the debug session by returning "undefined".
Another approach is to run the DA inside your Debugger extension. In this case the DA can directly access the vscode API to fetch data from your language extension and you do not have to "tunnel" the info from the extension to the DA. The mock-debugger has a simple to enable this mode. And the few lines of code that implement this approach are . This approach simplifies development (and debugging) because both the DA and the extension are one thing and you only need one debugger to debug both.
Another approach merges the Language Server and the Debug Adapter into one server that serves both protocols at the same time. Here the DA has easy access to Language Server info. The Java Debugger extension uses this approach because the language server and debugger are basically one component.
it would be great if you could that your debugger extension works with this Insiders build (that contains the : You don't have to uninstall anything. Just unzip the download into another folder and make sure that the official Insiders is not running when starting the new Insiders with the fix.
my apologies, I had to leave early on Friday. I'd need a Linux build to test your changes. What I'm about to try to is exactly what you'd described which is to move the code that gets the source/line information into the resolveDebugConfiguration method and send it over as an argument. The resolveDebugConfiguration already has an argument (the logfile) that's checked inside of the DA's launchArguments. I figure that the line breakpoint info, if passed over as an argument, can be verified there and error out in a similar manner if there's a problem. This should simply alleviate the message ping pong during the initialize. I'll post something shortly with my findings there. I guess the only question is whether or not I can await on an async function from inside resolveDebugConfiguration?
I've started a linux 64 build
here is the linux build in three archive formats:
The .deb file format works for me for an install. I just pulled it and I'll let you know as soon as I get my my environment setup (had to reboot so it'll be a few minutes)
My apologies for the delay. After installing your fix things work as they used to which is to say that we receive the event that's fired from inside of the initializeRequest. Will this be able to get into a code-insiders build as well as the code release that's scheduled for this week? I thank you for doing this and I do plan on trying to remove this interaction from our code in the very near future. Your change will give me some breathing room to do this. You'll have to pardon my previous bit of clueless but I did have to play around a bit to see what you were talking about with your suggestion of moving things into the resolveDebugConfiguration. I'm less familiar with that than I am with other areas of the DA/Extension code. I didn't realize that just adding that to the DebugConfiguration would allow automatically add it to the args passed into the launchRequest (this is partially because our own LaunchRequestArguments class limit what we see). The problem I believe that I'm going to run into with trying to fetch the source/line information, the way you'd suggested above with your resolveDebugConfiguration implementation is that that call to get the line breakpoint information "const lineBpInfo = await sfdxApex.exports.getLineBreakpointInfo();" is an async call. Unless I'm mistaken, I don't believe I can make that resolveDebugConfiguration function async due to the return type. With that being said, I believe I can use the same mechanism that we're getting to promt for a log file which is create a command that's execute that'll set the source line information in a similar manner.
thanks for verifying that my fix makes VS Code work again with your debugger. The plan is to include that fix in the next Insiders and the upcoming stable version. The method returns a which indicates that it is async. That means you can just either return a Promise or you can add "async" to your declaration if you want to use "await" in its body. I'm doing basically the same in node-debug too: All provider APIs are async by definition because they run in a different process anyway.
can we close this one and verify?
verified my fix already in a private build. I've verified again that my simulation of the issue is fixed in Insiders. Adding "verified" label.
the fix is now in Insiders. Could you please verify again that your debugger works in Insider. Thanks.
I've installed the latest insiders, here's (version information below) and things worked just fine. Just to confirm, this fix will also be in the code build released this week as well? This build information from the installed insiders build, just to confirm Version: 1.27.0-insider Commit: Date: 2018-09-03T09:15:27.927Z Electron: 2.0.7 Chrome: 61.0.3163.100 8.9.3 V8: 6.1.534.41 Architecture: x64 Am I correct in running under the assumption that this particular fix is temporary and is code you guys would ultimately like to remove? I've every intention of fixing this on our end within the next week or two (I'm in the middle of a feature that has a more immediate deadline). As for the resolveDebugConfiguration, you are correct, I'd gotten tangled in the return value vscode.ProviderResult<vscode.DebugConfigurationwhich just had to be changed to Promise<vscode.DebugConfiguration. Sometimes leaving and coming back to code with fresh eyes is a good thing.
thanks for verifying (again). Yes, this Insider will be promoted to Stable this week.
that's great news. Again, thanks for doing this, especially over a holiday weekend.
no holiday weekend here in Switzerland... :-( BTW, since we do not want to break extensions, we are not planning to remove the fix on our side any time soon. But I still recommend that you change the code on your side a bit to make it simpler and less likely to fail when we again refactor something on our side.
that's the plan. We'd like to prevent fire drills like this in the future. If you're ever in Redmond, I'm just over in Bellevue, drinks are on me.

Discussion:
this._toDispose = []; this._toDispose.push(debugService.onDidNewSession(session => { this._proxy.$acceptDebugSessionStarted(<DebugSessionUUID>session.getId(), session.configuration.type, session.getName(false)); <ins> })); this._toDispose.push(debugService.onWillNewSession(session => { // Need to start listening early to new session events because a custom event can come while a session is initialising </ins> this._toDispose.push(session.onDidCustomEvent(event => { if (event && event.sessionId) { <del> if (process) { this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, session.configuration.type, session.configuration.name, event); } </del> <ins> this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, session.configuration.type, session.configuration.name, event); </ins> } })); }));

onDidNewSession: Event<ISession>; /** <ins> * Allows to register on sessions about to be created (not yet fully initialised) */ onWillNewSession: Event<ISession>; /** </ins> * Allows to register on end session events. */ onDidEndSession: Event<ISession>;

private readonly _onDidChangeState: Emitter<State>; private readonly _onDidNewSession: Emitter<ISession>; <ins> private readonly _onWillNewSession: Emitter<ISession>; </ins> private readonly _onDidEndSession: Emitter<ISession>; private model: Model; private viewModel: ViewModel;

this.breakpointsToSendOnResourceSaved = new Set<string>(); this._onDidChangeState = new Emitter<State>(); this._onDidNewSession = new Emitter<ISession>(); <ins> this._onWillNewSession = new Emitter<ISession>(); </ins> this._onDidEndSession = new Emitter<ISession>(); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager);

return this._onDidNewSession.event; } <ins> get onWillNewSession(): Event<ISession> { return this._onWillNewSession.event; } </ins> get onDidEndSession(): Event<ISession> { return this._onDidEndSession.event; }

const dbgr = this.configurationManager.getDebugger(resolved.type); const session = this.instantiationService.createInstance(Session, sessionId, configuration, root, this.model); <ins> this._onWillNewSession.fire(session); </ins> this.allSessions.set(sessionId, session); return session.initialize(dbgr).then(() => { this.registerSessionListeners(session);

return null; } <ins> public get onWillNewSession(): Event<ISession> { return null; } </ins> public get onDidNewSession(): Event<ISession> { return null; }