vscode icon indicating copy to clipboard operation
vscode copied to clipboard

terminate tasks gracefully and not forcefully on closing VSCode or when using the "kill" (trashcan) button

Open Mutantpenguin opened this issue 1 year ago β€’ 9 comments

Does this issue occur when all extensions are disabled?: Yes

  • VS Code Version: 1.87.0 (and all versions before I can remember)
  • OS Version: Ubuntu 22.04.4

Steps to Reproduce:

I am not able to create a real minimal example, but will show my exact usecase here and try to explain it.

  1. create a task with the command bench start from frappe bench (https://github.com/frappe/bench) and run it
    • this starts 3 redis servers, a node server, a yarn watcher and some other stuff
  2. use the "kill" button for the task or close VSCode
  3. bench start got terminated, but all the processes it itself spawned are still running
  4. reopening VSCode and starting the task again will error out, because the still running redis servers are blocking some necessary ports
    • even if it would work, we would still have multiple resource hogging processes lingering in the background

Pressing CTRL-C instead in the tasks terminal gracefully shuts it down and bench start is able to do its proper teardown of all the spawned processes. I am not invested at all in bench so can't answer specific questions to it, but I know that it is using honcho and a Procfile to managing all these processes.

So I guess all I am asking for is a way for tasks to not be forcefully killed, but do the same as CTRL-C in the console would do to it.

This seems to describe that the same functionality already exists for launch.json: https://github.com/microsoft/vscode-js-debug/issues/630#issuecomment-732414805

Mutantpenguin avatar Mar 01 '24 12:03 Mutantpenguin

This issue here is similar to mine but only concerned about the kill button, and not closing VSCode. https://github.com/microsoft/vscode/issues/160740

Mutantpenguin avatar Mar 01 '24 13:03 Mutantpenguin

This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our documentation.

Happy Coding!

vscodenpa avatar Mar 04 '24 20:03 vscodenpa

I agree with this. I guess terminate task using SIGKILL which can't be trapped. I need execute some commands to clean the tmp files created by this task

fanmengmen avatar Mar 27 '24 00:03 fanmengmen

:slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our documentation.

Happy Coding!

vscodenpa avatar Apr 13 '24 02:04 vscodenpa

Great, I am really glad that it reached the backlog!

If I may make a recommendation:

  • I think the current way should stay as the default way to always be backwards compatible.
  • What I and others want here should be an optional and explicit setting for a task.

Mutantpenguin avatar Apr 17 '24 09:04 Mutantpenguin

This is especially useful for long running tasks - I've ran across it while creating a task that requires specific ports and runs a clean up process on SIGINT (eg: Firebase Emulator).

If I stopped the task by clicking trash icon, I found that the process had continued in the background, which meant the required ports were still in use and had to be killed individually using lsof -I :$PORT_NUMBER to identify the ID of the underlying Java process, then kill $PID. Doing that for 9 separate ports every time you need to restart the emulator is less than ideal. 😬

Totally agree with @Mutantpenguin - this should be an explicit setting defined on a task by task basis.

cjlryan avatar Jun 14 '24 13:06 cjlryan

Please make an option for this to work with SIGBREAK. I need to terminate our build tools, which have SIGBREAK handlers (just like Ctrl-C in the command prompt) clean up the build and write out partially built data. It's been a PITA for me since Terminate just kills the process.

tmeekins avatar Jun 17 '24 19:06 tmeekins

Hi VSCode team. Is there any updates about this issue? I also met this issue in our VSCode extension.

lijie-lee avatar Nov 05 '24 02:11 lijie-lee

This has been an issue for me for the last few months using VS Code, not sure exactly what version it started, but after updating today it got even worse. I'm developing on Windows with NodeJS (Express backend, Angular frontend, running as tasks within VS Code), every time I close the window, close the workspace, kill tasks or restart running tasks it does not free the port (previous to today's update, this only occurred if I closed the window). If I gracefully kill the tasks within the terminal windows manually (Ctrl+C for my Express backend server, q+enter in the ng serve frontend server), everything works fine.

I've been blindly killing Node processes within Task Manager when this happens, but after today's update I got annoyed and wrote a little PowerShell script (and an accompanying VS Code task!) to automate this process. Hopefully this is resolved eventually, but until then here's the script/task I'm using:

PowerShell Script:

Param(
    [int] $PortNumber,

    [ValidateSet("TCP", "UDP", IgnoreCase = $true)]
    [string] $Protocol = "TCP"
)

$Protocol = $Protocol.ToUpper()

$processId = $null

if ($Protocol -eq "TCP") {
    try {
        $connections = Get-NetTCPConnection -LocalPort $PortNumber -ErrorAction SilentlyContinue
        if ($connections -is [System.Collections.IEnumerable]) {
            $processId = $connections[0].OwningProcess
        }
        else {
            $processId = $connections.OwningProcess
        }
    }
    catch {
        # Ignore error if no process is using the port
    }
}
elseif ($Protocol -eq "UDP") {
    try {
        $connections = Get-NetUDPEndpoint -LocalPort $PortNumber -ErrorAction SilentlyContinue
        if ($connections -is [System.Collections.IEnumerable]) {
            $processId = $connections[0].OwningProcess
        }
        else {
            $processId = $connections.OwningProcess
        }
    }
    catch {
        # Ignore error if no process is using the port
    }
}

if ($null -eq $processId) {
    Write-Host "No process is using $Protocol port $PortNumber"
    return
}

$process = Get-Process -Id $processId

Write-Host "Killing $($process.ProcessName) process (PID: ${processId}) using ${Protocol} port ${PortNumber}..."

taskkill /F /PID $processId

And then you can run this easily within VS Code by creating a task. This task assumes the above script is saved as Stop-ProcessUsingPort.ps1 saved in C:\temp, but you can adjust the properties as needed

{
    "version": "2.0.0",
    "tasks": [
    // Other tasks...
        {
            "label": "free-port",
            "detail": "Free up a port that is currently in use.",
            "type": "shell",
            "command": "./Stop-ProcessUsingPort.ps1 -Protocol ${input:protocol} -PortNumber ${input:portNumber}",
            "options": {
                "cwd": "C:\\temp"
            },
            "problemMatcher": [],
            "presentation": {
                "close": false
            },
            "icon": {
                "id": "debug-disconnect",
                "color": "terminal.ansiRed"
            }
        }
    ],
    "inputs": [
    // Other inputs...
        {
            "id": "protocol",
            "type": "pickString",
            "description": "Select the protocol the port is using",
            "default": "TCP",
            "options": [
               {
                    "value": "TCP",
                    "label": "Transmission Control Protocol"
                },
                {
                    "value": "UDP",
                    "label": "User Datagram Protocol"
                }
            ]
        },
        {
            "id": "portNumber",
            "type": "promptString",
            "description": "Enter the port number to free",
            "default": "3002"
        }
    ]
}

BenAldebert avatar Mar 05 '25 21:03 BenAldebert

I'm thinking this opt-in setting should apply not only to tasks, but also terminals.

meganrogge avatar May 14 '25 18:05 meganrogge

I initially added a setting to change the signal sent when killing terminals, but after further discussion, we believe this isn't the right long-term approach. Terminal processes are expected to handle SIGHUP appropriately, whether that means exiting cleanly or ignoring it. Automatically replacing SIGHUP with SIGTERM shifts that responsibility away from the process and may lead to unintended side effects. As a result, I’m reverting this change. Instead, we’re considering exposing a command that would allow users to send specific signals (like SIGTERM) manually, https://github.com/microsoft/vscode/issues/250669.

The command would enable creating a keybinding to do this and an extension could add a button.

meganrogge avatar Jun 04 '25 19:06 meganrogge

I am really sorry to hear about the revert and the proposed alternative solution. πŸ™

This might solve the problem of closing a terminal manually (not really sure since the existing button would have to be replaced?), but would not solve correct handling of terminals when closing VSCode.

I also don't understand Automatically replacing SIGHUP with SIGTERM as the new functionality was opt in.

Mutantpenguin avatar Jun 05 '25 09:06 Mutantpenguin

but would not solve correct handling of terminals when closing VSCode.

@Mutantpenguin the main thing I took from this when we looked into this yesterday was SIGHUP is literally the only correct handling of this. If you have something sticking around afterwards, it's because that's how the application behaves intentionally (like a daemon), and you should use the regular methods to handle that. If we offered this setting, I 100% foresee people complaining that the terminal is closing their long running daemon, leading to more support work for us.

So the solution is instead of making this niche (and arguably wrong) use case a core feature, provide a simple primitive to allow users to do this if they so choose.

Currently a work in progress but there will be this command:

Image

Which you can keybind:

{
    "key": "ctrl+alt+c",
    "command": "workbench.action.terminal.sendSignal",
    "args": { "signal": "SIGTERM" },
    "when": "terminalFocus"
}

And an extension could then easily leverage that command to create a button near the terminal.

Part of managing an open source project is knowing when to say no, and this is one of those cases as this feature would be harmful due to added complexity in core kill logic, bloat on the user side, additional work on our team to support the inevitable complaints, etc.

Tyriar avatar Jun 05 '25 12:06 Tyriar

Just a quick suggestion: what if a new option terminationSignal is added to individual task in tasks.json? So when the trashcan icon is clicked or "Tasks: Terminate Task" is used in command palette β†’ VS Code would send that signal instead. VS Code could also force terminate after X seconds.

@Mutantpenguin will that resolve your issue?

mxpshn avatar Jun 07 '25 10:06 mxpshn

We want the trash can icon to work as it does in all other terminals and send SIGHUP. You can assign a keybinding or add a button via an extension as described above to achieve what you're hoping for.

meganrogge avatar Jun 09 '25 00:06 meganrogge