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.78.0 and older and (with slightly different behavior) insiders as of 5/4/2023 OS Version: MacOS 13.3.1 (a) Steps to Reproduce: Use these commands to produce and launch a suitable project: First bug: in either 1.78.0 or the nightly, use to launch , then hit Cmd-Shift-B to launch . You end up with two copies of running, when it should have an implicit of 1. Hit Cmd-Shift-B again, and it will tell you you've hit the instance limit. The problem here is that dependent tasks don't count towards the instance limit. Second bug: Now add to , and repeat the above. There is no change in the nightly (as you might expect), but surprisingly 1.78.0 now reports that it can't start a second copy. But thats just a bug; if you change it to and repeat, 1.78.0, still tells you it can't launch a second copy. This bug was fixed by , so I'm just including it for completeness. Third bug: Add to . Now hit Cmd-Shift-B to launch , then use to launch . will (correctly) re-use the already running task, rather than launching a new one. Now kill all the terminals (to make the next step less confusing). Hit Cmd-Shift-B to launch sleeper, wait 5 seconds, and hit Cmd-Shift-B again, wait another 5 seconds for the first task to finish, and then run . Now, although the second task is still running, starts a new one. The issue here is that when looking for an existing dependent task, uses the task's , which only matches the first instance of a task with greater than 1 (and here, that task has already finished).

Discussion:
group?: string; } <ins> interface IInstanceCount { count: number; } </ins> interface IActiveTerminalData { terminal?: ITerminalInstance; task: Task; promise: Promise<ITaskSummary>; state?: TaskEventKind; <ins> count: IInstanceCount; </ins> } interface IReconnectionTaskData {

const ReconnectionType = 'Task'; <del> class InstanceManager { private _currentInstances: number = 0; private _counter: number = 0; addInstance() { this._currentInstances++; this._counter++; } removeInstance() { this._currentInstances--; } get instances() { return this._currentInstances; } get counter() { return this._counter; } } </del> class VariableResolver { private static _regex = /${(.*?)}/g; constructor(public workspaceFolder: IWorkspaceFolder | undefined, public taskSystemInfo: ITaskSystemInfo | undefined, public readonly values: Map<string, string>, private _service: IConfigurationResolverService | undefined) {

}; private _activeTasks: IStringDictionary<IActiveTerminalData>; <del> private _instances: IStringDictionary<InstanceManager>; </del> private _busyTasks: IStringDictionary<Task>; private _terminals: IStringDictionary<ITerminalData>; private _idleTaskTerminals: LinkedMap<string, string>;

super(); this._activeTasks = Object.create(null); <del> this._instances = Object.create(null); </del> this._busyTasks = Object.create(null); this._terminals = Object.create(null); this._idleTaskTerminals = new LinkedMap<string, string>();

public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { task = task.clone(); // A small amount of task state is stored in the task (instance) and tasks passed in to run may have that set already. <del> const recentTaskKey = task.getRecentlyUsedKey() ?? ''; const validInstance = !this._instances[recentTaskKey] || this._instances[recentTaskKey].instances < ((task.runOptions && task.runOptions.instanceLimit) ?? 0); const instance = this._instances[recentTaskKey] ? this._instances[recentTaskKey].instances : 0; </del> <ins> const instances = InMemoryTask.is(task) || this._isTaskEmpty(task) ? [] : this._getInstances(task); const validInstance = instances.length < ((task.runOptions && task.runOptions.instanceLimit) ?? 1); const instance = instances[0]?.count?.count ?? 0; </ins> this._currentTask = new VerifiedTask(task, resolver, trigger); if (instance > 0) { <del> task.instance = this._instances[recentTaskKey].counter; </del> <ins> task.instance = instance; </ins> } <del> const lastTaskInstance = this.getLastInstance(task); const terminalData = lastTaskInstance ? this._activeTasks[lastTaskInstance.getMapKey()] : undefined; </del> if (!validInstance) { <del> if (terminalData && terminalData.promise) { this._lastTask = this._currentTask; return { kind: TaskExecuteKind.Active, task: terminalData.task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise }; } throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask); </del> <ins> const terminalData = instances[instances.length - 1]; this._lastTask = this._currentTask; return { kind: TaskExecuteKind.Active, task: terminalData.task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise }; </ins> } try {

executeResult.promise.then(summary => { this._lastTask = this._currentTask; }); <del> if (InMemoryTask.is(task) || !this._isTaskEmpty(task)) { if (!this._instances[recentTaskKey]) { this._instances[recentTaskKey] = new InstanceManager(); } this._instances[recentTaskKey].addInstance(); } </del> return executeResult; } catch (error) { if (error instanceof TaskError) {

}); } <del> private _removeInstances(task: Task) { const recentTaskKey = task.getRecentlyUsedKey() ?? ''; if (this._instances[recentTaskKey]) { this._instances[recentTaskKey].removeInstance(); if (this._instances[recentTaskKey].instances === 0) { delete this._instances[recentTaskKey]; } } </del> <ins> private _getInstances(task: Task): IActiveTerminalData[] { const recentKey = task.getRecentlyUsedKey(); return Object.values(this._activeTasks).filter( (value) => recentKey && recentKey === value.task.getRecentlyUsedKey()); </ins> } private _removeFromActiveTasks(task: Task | string): void {

return; } delete this._activeTasks[key]; <del> this._removeInstances(taskToRemove.task); </del> } private _fireTaskEvent(event: ITaskEvent) {

} else { taskResult = encounteredTasks.get(commonKey); if (!taskResult) { <del> const key = dependencyTask.getMapKey(); taskResult = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; </del> <ins> const activeTask = this._activeTasks[dependencyTask.getMapKey()] ?? this._getInstances(dependencyTask).pop(); taskResult = activeTask && this._getDependencyPromise(activeTask); </ins> } } if (!taskResult) {

} } <del> if ((ContributedTask.is(task) || CustomTask.is(task)) && (task.command)) { return Promise.all(promises).then((summaries): Promise<ITaskSummary> | ITaskSummary => { for (const summary of summaries) { if (summary.exitCode !== 0) { this._removeInstances(task); return { exitCode: summary.exitCode }; } </del> <ins> return Promise.all(promises).then((summaries): Promise<ITaskSummary> | ITaskSummary => { for (const summary of summaries) { if (summary.exitCode !== 0) { return { exitCode: summary.exitCode }; </ins> } <ins> } if ((ContributedTask.is(task) || CustomTask.is(task)) && (task.command)) { </ins> if (this._isRerun) { return this._reexecuteCommand(task, trigger, alreadyResolved!); } else { return this._executeCommand(task, trigger, alreadyResolved!); } <del> }); } else { return Promise.all(promises).then((summaries): ITaskSummary => { for (const summary of summaries) { if (summary.exitCode !== 0) { return { exitCode: summary.exitCode }; } } return { exitCode: 0 }; }); } </del> <ins> } return { exitCode: 0 }; }); </ins> }).finally(() => { if (this._activeTasks[mapKey] === activeTask) { delete this._activeTasks[mapKey]; } }); <del> const activeTask = { task, promise }; </del> <ins> const lastInstance = this._getInstances(task).pop(); const count = lastInstance?.count ?? { count: 0 }; count.count++; const activeTask = { task, promise, count }; </ins> this._activeTasks[mapKey] = activeTask; return promise; }