Repository: microsoft/debugpy

Issue:
multiprocessing triggers preLaunchTask and postDebugTask more than once
## Environment data

- DEBUGPY version: 1.0.0b4
- OS and version: ubuntu18.04
- Python version (& distribution if applicable, e.g. Anaconda): Python 3.6.9
- Using VS Code or Visual Studio:VS Code

## Actual behavior

When I launch a simple program that involves `multiprocessing`, upon firing of another process, the preLaunchTask runs more than one time (once per process as I understand). Same goes for the postDebugTask, which seems to run more than one time. 

## Expected  behavior

The `preLaunchTask` should run once only, as well as the `postDebugTask`.

## Steps to reproduce:
1. Get this in `launch.json` :
```
        {
            "name": "Python: mp.py",
            "type": "python",
            "request": "launch",
            "preLaunchTask": "testStartTask",
            "program": "${workspaceFolder}/testing_code/mp.py",
            "console": "integratedTerminal",
            "debugAdapterPath": "/home/omartin/proj/debugpy/src/debugpy/adapter",
            "postDebugTask": "testStopTask"
        }
```
2. Get this in `tasks.json`:
```
        {
            "label": "testStartTask",
            "type": "shell",
            "isBackground": false,
            "command":[
                "echo helloStart `date`"
            ],
            "options": {
                "shell": {
                    "executable": "/bin/bash",
                    "args": [
                        "-c"
                    ]
                }
            },
            "problemMatcher": [
                "$eslint-compact"
            ]
        },
        {
            "label": "testStopTask",
            "type": "shell",
            "isBackground": false,
            "command":[
                "echo helloStop `date`"
            ],
            "options": {
                "shell": {
                    "executable": "/bin/bash",
                    "args": [
                        "-c"
                    ]
                }
            },
            "problemMatcher": [
                "$eslint-compact"
            ]
        },
```
3. `mp.py` looks like this
```
from multiprocessing import Pool
import traceback


def f(x):
    return x * x


if __name__ == '__main__':
    # start 4 worker processes
    try:
        with Pool(processes=4) as pool:

            # print "[0, 1, 4,..., 81]"
            print(pool.map(f, range(10)))
            print("For the moment, the pool remains available for more work")

    except:
        print(traceback.print_exc())
    # exiting the 'with'-block has stopped the pool
    print("Now the pool is closed and no longer available")

```
I also needed the fix for `connection refused error` that I have in my version of `debug.py`.

It seems, by the output of this, that when my process forks, it runs the `preLaunchTask` a 2nd time, but for each process that terminates (4 in this example), it runs the `postDebugTask`.

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=103&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100.png' alt='100.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=103&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py
index 9ece52da..0acc1e13 100644
--- a/src/debugpy/adapter/clients.py
+++ b/src/debugpy/adapter/clients.py
@@ -305,7 +305,9 @@ class Client(components.Component):
             raise request.cant_handle('"sudo":true is not supported on Windows.')
 
         servers.serve()
-        launchers.spawn_debuggee(self.session, request, args, console, console_title, sudo)
+        launchers.spawn_debuggee(
+            self.session, request, args, console, console_title, sudo
+        )
 
     @_start_message_handler
     def attach_request(self, request):
@@ -499,8 +501,9 @@ class Client(components.Component):
             body = dict(self.start_request.arguments)
             self._known_subprocesses.add(conn)
 
-        body.pop("processId", None)
-        body.pop("listen", None)
+        for key in "processId", "listen", "preLaunchTask", "postDebugTask":
+            body.pop(key, None)
+
         body["name"] = fmt("Subprocess {0}", conn.pid)
         body["request"] = "attach"
         body["subProcessId"] = conn.pid


Test patch:
diff --git a/tests/debug/config.py b/tests/debug/config.py
index d6203b54..c2582361 100644
--- a/tests/debug/config.py
+++ b/tests/debug/config.py
@@ -39,6 +39,8 @@ class DebugConfig(collections.MutableMapping):
         "name": (),
         "noDebug": False,
         "pathMappings": [],
+        "postDebugTask": (),
+        "preLaunchTask": (),
         "pyramid": False,
         "pythonPath": (),
         "redirectOutput": False,
diff --git a/tests/debugpy/test_multiproc.py b/tests/debugpy/test_multiproc.py
index 0a0f86bb..f8d67749 100644
--- a/tests/debugpy/test_multiproc.py
+++ b/tests/debugpy/test_multiproc.py
@@ -15,6 +15,7 @@ from tests.patterns import some
 
 
 if not tests.full:
+
     @pytest.fixture(params=[runners.launch] + runners.all_attach_socket)
     def run(request):
         return request.param
@@ -120,10 +121,7 @@ def test_multiprocessing(pyfile, target, run, start_method):
                 "name": some.str,
                 "request": "attach",
                 "subProcessId": some.int,
-                "connect": {
-                    "host": some.str,
-                    "port": some.int,
-                }
+                "connect": {"host": some.str, "port": some.int},
             }
         )
 
@@ -142,10 +140,7 @@ def test_multiprocessing(pyfile, target, run, start_method):
                     "name": some.str,
                     "request": "attach",
                     "subProcessId": some.int,
-                    "connect": {
-                        "host": some.str,
-                        "port": some.int,
-                    }
+                    "connect": {"host": some.str, "port": some.int},
                 }
             )
 
@@ -196,6 +191,9 @@ def test_subprocess(pyfile, target, run, subProcess):
 
     with debug.Session() as parent_session:
         backchannel = parent_session.open_backchannel()
+
+        parent_session.config["preLaunchTask"] = "doSomething"
+        parent_session.config["postDebugTask"] = "doSomethingElse"
         if subProcess is not None:
             parent_session.config["subProcess"] = subProcess
 
@@ -203,16 +201,14 @@ def test_subprocess(pyfile, target, run, subProcess):
             pass
 
         expected_child_config = dict(parent_session.config)
-        expected_child_config.pop("listen", None)
+        for key in "processId", "listen", "preLaunchTask", "postDebugTask":
+            expected_child_config.pop(key, None)
         expected_child_config.update(
             {
                 "name": some.str,
                 "request": "attach",
                 "subProcessId": some.int,
-                "connect": {
-                    "host": some.str,
-                    "port": some.int,
-                }
+                "connect": {"host": some.str, "port": some.int},
             }
         )