Change:
<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: 1.63.2 OS Version: macOS 12.1 (ARM64) I can't get more than two launch configurations to run at the same time. I can run the rest of the configurations afterwards and everything works (debugging, etc.). StopAll will also only close the two configs that were successfully launched. I haven't found any mention in the documentation that this is limited to two launch configurations. I've only seen that two are used in every explanation or tutorial about this feature. Steps to Reproduce: multiple launch configurations in multi-root workspace inside of project roots. launch configurations to compounds in .code-workspace file debugging two of the many compound configurations will run Examples:
VS Code imposes no limit on number of launch configurations in compound config. I've verified this with this simple example project (which only relies on built-in VS Code features and not third-party extensions): unpack zip file in VS Code use "Open Workspace from file" to open workspace file inside folder switch to debug viewlet in debug config dropdown select and run "4 Debug Session" Observe: 4 debug sessions: <img width="1384" alt="CleanShot 2022-01-26 at 11 45 23 src="https://user-"Maybe there is a limit on dotnet/coreclr?
Thank you for taking a look at this After inspecting your configuration and some research in my own project I was able to isolate the culprit. The problem arises when you try to make use of inputs in the launch configuration. I created a demo for download, the process is the same as you described above.
thanks for the additional info. I was able to create an even smaller repro without a multi-folder workspace: unpack zip file in VS Code open the "compound" folder switch to debug viewlet in debug config dropdown select and run "Compound" Observe: only one debug session starts
Is it possible to implement the input that allows blocking another input request until the first input has completed? Latter quick pick also cancels former ones so it needs to be fixed as well.
Do you own the configuration resolver Is my solution correct or should the service guarantee correct behavior when calling into it multiple times simultaneously?
your solution looks good. The configuration resolver service doesn't currently make any guarantees about what will happen if it's called into multiple time simultaneously. I would also be fine with the change being in the configuration resolver service though. We can change it's behavior so that it does make that guarantee.
Yes, making the fix in the configuration resolver service sounds better.
This fix doesn't fully cover our scenario (tested in latest insiders). This fixes case, and yet we also prompt for input or quick pick in , which is not covered in this PR. Is it appropriate to wait for as well?
Good point. Yeah, I think that makes sense.
I guess we'll need Queue's for and Would you like to make the changes, or should I work on this?
I can take care of it
This gets a bit weird. Since one launch config can be calling into the variable resolver (the first fix) while the second is calling the extension config provider, we also need a queue that orchestrates those two things. Easiest fix is to start launch configs one at a time instead of kicking them off in parallel: and I'm not sure what other impacts that could have. Perf to some extent, probably. So I wonder whether we would ever consider queueing calls into the quick pick control so that invoking the quick pick while it is already visible would wait instead of cancelling the first one as it does currently. Example of what I'm talking about (but in my real situation it's an extension competing with code in core): This extension code only shows one input box, while we could have the second wait for the first to complete. This could possibly be a breaking change for some extensions though, so it could be enabled with a flag.
As an easy workaround, I can put a queue around calling into the vscode variable resolver and calling an extension debug config provider. But, putting a queue around two unrelated pieces of code seems to say that I'm solving the problem at the wrong level.
Or maybe make them the same level by moving the resolve queue to
Just to be clear. The two callers of the are in two different places right (core and extension)?
Right
Ok I've not heard of any feature request like this but feel free to open one... I wonder if maybe instead Core could have concept of "important" quick picks that can't be stomped over by extensions and instead the extension calls wait until Core is done. Then we don't need any more API
It can also go the other way. The problem is just that there are two async processes that have to run and show a quick input, and we need both of them to complete and not to get canceled by the other. Filed I'll go with a workaround for now, so I'm not blocked, just figured I'd start the discussion
Sorry for the late reply. I tested this today and it works for our scenario. Thanks for the quick fixes!

Discussion:
import * as aria from 'vs/base/browser/ui/aria/aria'; import { Action, IAction } from 'vs/base/common/actions'; import { distinct } from 'vs/base/common/arrays'; <del> import { raceTimeout, RunOnceScheduler } from 'vs/base/common/async'; </del> <ins> import { Queue, raceTimeout, RunOnceScheduler } from 'vs/base/common/async'; </ins> import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isErrorWithActions } from 'vs/base/common/errorMessage'; import * as errors from 'vs/base/common/errors';

private activity: IDisposable | undefined; private chosenEnvironments: { [key: string]: string }; <ins> private configResolverQueue = new Queue<IConfig | null | undefined>(); </ins> constructor( @IEditorService private readonly editorService: IEditorService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,

const sessionId = generateUuid(); this.sessionCancellationTokens.set(sessionId, initCancellationToken); <del> const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token); </del> <ins> const configByProviders = await this.configResolverQueue.queue(() => this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token)); </ins> // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try {

return false; } <del> const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); </del> <ins> const cfg = await this.configResolverQueue.queue(() => this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig!, initCancellationToken.token)); </ins> if (!cfg) { if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) {	// show launch.json only for "config" being "null". await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);

if (launch && needsToSubstitute && unresolved) { const initCancellationToken = new CancellationTokenSource(); this.sessionCancellationTokens.set(session.getId(), initCancellationToken); <del> const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); </del> <ins> const resolvedByProviders = await this.configResolverQueue.queue(() => this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved!.type, unresolved!, initCancellationToken.token)); </ins> if (resolvedByProviders) { resolved = await this.substituteVariables(launch, resolvedByProviders); if (resolved && !initCancellationToken.token.isCancellationRequested) { <del> resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); </del> <ins> resolved = await this.configResolverQueue.queue(() => this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved!.type, resolved!, initCancellationToken.token)); </ins> } } else { resolved = resolvedByProviders;

return Promise.all(sessions.map(s => disconnect ? s.disconnect(undefined, suspend) : s.terminate())); } <del> private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> { </del> <ins> private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | null | undefined> { </ins> const dbg = this.adapterManager.getDebugger(config.type); if (dbg) { let folder: IWorkspaceFolder | undefined = undefined;

} } try { <del> return await dbg.substituteVariables(folder, config); </del> <ins> return this.configResolverQueue.queue(() => dbg.substituteVariables(folder, config)); </ins> } catch (err) { this.showError(err.message, undefined, !!launch?.getConfiguration(config.name)); return undefined;	// bail out