We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Interrupting an `(Interactive)Process` should attempt to kill the process gracefully
Currently, both `Process` and `InteractiveProcess` will send `KILL` when they are dropped (via `tokio`s facilities for `kill_on_drop`). But in both cases, kill doesn't give the underlying process time to do its own teardown, and this can lead to resource leaks. `pants` itself has [concerns](https://github.com/pantsbuild/pants/issues/12996) with being killed without getting a TERM first, and end user processes like `uvicorn` would like to be able to run their user shutdown hooks before dying.

To do this, we will likely need to implement a workaround for the lack of `AsyncDrop`: by essentially adding an optional `TERM` and synchronous wait in a `Drop` guard wrapped around relevant processes, or optionally gracefully interrupting graph nodes.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `testprojects/src/python/coordinated_runs/waiter.py`
Content:
```
1 # Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
2 # Licensed under the Apache License, Version 2.0 (see LICENSE).
3 
4 
5 import os
6 import sys
7 import time
8 from multiprocessing import Process
9 
10 waiting_for_file = sys.argv[1]
11 pid_file = sys.argv[2]
12 child_pid_file = sys.argv[3]
13 attempts = 60
14 
15 
16 def run_child():
17     while True:
18         print("Child running...")
19         time.sleep(1)
20 
21 
22 child = Process(target=run_child, daemon=True)
23 child.start()
24 
25 with open(child_pid_file, "w") as pf:
26     pf.write(str(child.pid))
27 
28 with open(pid_file, "w") as pf:
29     pf.write(str(os.getpid()))
30 
31 try:
32     while not os.path.isfile(waiting_for_file):
33         if attempts <= 0:
34             raise Exception("File was never written.")
35         attempts -= 1
36         sys.stderr.write("Waiting for file {}\n".format(waiting_for_file))
37         time.sleep(1)
38 finally:
39     child.terminate()
40
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/testprojects/src/python/coordinated_runs/waiter.py b/testprojects/src/python/coordinated_runs/waiter.py
--- a/testprojects/src/python/coordinated_runs/waiter.py
+++ b/testprojects/src/python/coordinated_runs/waiter.py
@@ -7,11 +7,6 @@
 import time
 from multiprocessing import Process
 
-waiting_for_file = sys.argv[1]
-pid_file = sys.argv[2]
-child_pid_file = sys.argv[3]
-attempts = 60
-
 
 def run_child():
     while True:
@@ -19,21 +14,46 @@
         time.sleep(1)
 
 
-child = Process(target=run_child, daemon=True)
-child.start()
+def main():
+    waiting_for_file = sys.argv[1]
+    pid_file = sys.argv[2]
+    child_pid_file = sys.argv[3]
+    cleanup_wait_time = int(sys.argv[4])
+    attempts = 60
 
-with open(child_pid_file, "w") as pf:
-    pf.write(str(child.pid))
+    child = Process(target=run_child, daemon=True)
+    child.start()
 
-with open(pid_file, "w") as pf:
-    pf.write(str(os.getpid()))
+    with open(child_pid_file, "w") as pf:
+        pf.write(str(child.pid))
 
-try:
-    while not os.path.isfile(waiting_for_file):
-        if attempts <= 0:
-            raise Exception("File was never written.")
-        attempts -= 1
-        sys.stderr.write("Waiting for file {}\n".format(waiting_for_file))
-        time.sleep(1)
-finally:
-    child.terminate()
+    with open(pid_file, "w") as pf:
+        pf.write(str(os.getpid()))
+
+    try:
+        while not os.path.isfile(waiting_for_file):
+            if attempts <= 0:
+                raise Exception("File was never written.")
+            attempts -= 1
+            sys.stderr.write("Waiting for file {}\n".format(waiting_for_file))
+            sys.stderr.flush()
+            time.sleep(1)
+
+    except KeyboardInterrupt:
+        sys.stderr.write("keyboard int received\n")
+        sys.stderr.flush()
+
+    finally:
+        sys.stderr.write("waiter cleaning up\n")
+        sys.stderr.flush()
+
+        child.terminate()
+        if cleanup_wait_time > 0:
+            time.sleep(cleanup_wait_time)
+
+        sys.stderr.write("waiter cleanup complete\n")
+        sys.stderr.flush()
+
+
+if __name__ == "__main__":
+    main()

{"golden_diff": "diff --git a/testprojects/src/python/coordinated_runs/waiter.py b/testprojects/src/python/coordinated_runs/waiter.py\n--- a/testprojects/src/python/coordinated_runs/waiter.py\n+++ b/testprojects/src/python/coordinated_runs/waiter.py\n@@ -7,11 +7,6 @@\n import time\n from multiprocessing import Process\n \n-waiting_for_file = sys.argv[1]\n-pid_file = sys.argv[2]\n-child_pid_file = sys.argv[3]\n-attempts = 60\n-\n \n def run_child():\n     while True:\n@@ -19,21 +14,46 @@\n         time.sleep(1)\n \n \n-child = Process(target=run_child, daemon=True)\n-child.start()\n+def main():\n+    waiting_for_file = sys.argv[1]\n+    pid_file = sys.argv[2]\n+    child_pid_file = sys.argv[3]\n+    cleanup_wait_time = int(sys.argv[4])\n+    attempts = 60\n \n-with open(child_pid_file, \"w\") as pf:\n-    pf.write(str(child.pid))\n+    child = Process(target=run_child, daemon=True)\n+    child.start()\n \n-with open(pid_file, \"w\") as pf:\n-    pf.write(str(os.getpid()))\n+    with open(child_pid_file, \"w\") as pf:\n+        pf.write(str(child.pid))\n \n-try:\n-    while not os.path.isfile(waiting_for_file):\n-        if attempts <= 0:\n-            raise Exception(\"File was never written.\")\n-        attempts -= 1\n-        sys.stderr.write(\"Waiting for file {}\\n\".format(waiting_for_file))\n-        time.sleep(1)\n-finally:\n-    child.terminate()\n+    with open(pid_file, \"w\") as pf:\n+        pf.write(str(os.getpid()))\n+\n+    try:\n+        while not os.path.isfile(waiting_for_file):\n+            if attempts <= 0:\n+                raise Exception(\"File was never written.\")\n+            attempts -= 1\n+            sys.stderr.write(\"Waiting for file {}\\n\".format(waiting_for_file))\n+            sys.stderr.flush()\n+            time.sleep(1)\n+\n+    except KeyboardInterrupt:\n+        sys.stderr.write(\"keyboard int received\\n\")\n+        sys.stderr.flush()\n+\n+    finally:\n+        sys.stderr.write(\"waiter cleaning up\\n\")\n+        sys.stderr.flush()\n+\n+        child.terminate()\n+        if cleanup_wait_time > 0:\n+            time.sleep(cleanup_wait_time)\n+\n+        sys.stderr.write(\"waiter cleanup complete\\n\")\n+        sys.stderr.flush()\n+\n+\n+if __name__ == \"__main__\":\n+    main()\n", "issue": "Interrupting an `(Interactive)Process` should attempt to kill the process gracefully\nCurrently, both `Process` and `InteractiveProcess` will send `KILL` when they are dropped (via `tokio`s facilities for `kill_on_drop`). But in both cases, kill doesn't give the underlying process time to do its own teardown, and this can lead to resource leaks. `pants` itself has [concerns](https://github.com/pantsbuild/pants/issues/12996) with being killed without getting a TERM first, and end user processes like `uvicorn` would like to be able to run their user shutdown hooks before dying.\r\n\r\nTo do this, we will likely need to implement a workaround for the lack of `AsyncDrop`: by essentially adding an optional `TERM` and synchronous wait in a `Drop` guard wrapped around relevant processes, or optionally gracefully interrupting graph nodes.\n", "before_files": [{"content": "# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\n\nimport os\nimport sys\nimport time\nfrom multiprocessing import Process\n\nwaiting_for_file = sys.argv[1]\npid_file = sys.argv[2]\nchild_pid_file = sys.argv[3]\nattempts = 60\n\n\ndef run_child():\n    while True:\n        print(\"Child running...\")\n        time.sleep(1)\n\n\nchild = Process(target=run_child, daemon=True)\nchild.start()\n\nwith open(child_pid_file, \"w\") as pf:\n    pf.write(str(child.pid))\n\nwith open(pid_file, \"w\") as pf:\n    pf.write(str(os.getpid()))\n\ntry:\n    while not os.path.isfile(waiting_for_file):\n        if attempts <= 0:\n            raise Exception(\"File was never written.\")\n        attempts -= 1\n        sys.stderr.write(\"Waiting for file {}\\n\".format(waiting_for_file))\n        time.sleep(1)\nfinally:\n    child.terminate()\n", "path": "testprojects/src/python/coordinated_runs/waiter.py"}], "after_files": [{"content": "# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\n\nimport os\nimport sys\nimport time\nfrom multiprocessing import Process\n\n\ndef run_child():\n    while True:\n        print(\"Child running...\")\n        time.sleep(1)\n\n\ndef main():\n    waiting_for_file = sys.argv[1]\n    pid_file = sys.argv[2]\n    child_pid_file = sys.argv[3]\n    cleanup_wait_time = int(sys.argv[4])\n    attempts = 60\n\n    child = Process(target=run_child, daemon=True)\n    child.start()\n\n    with open(child_pid_file, \"w\") as pf:\n        pf.write(str(child.pid))\n\n    with open(pid_file, \"w\") as pf:\n        pf.write(str(os.getpid()))\n\n    try:\n        while not os.path.isfile(waiting_for_file):\n            if attempts <= 0:\n                raise Exception(\"File was never written.\")\n            attempts -= 1\n            sys.stderr.write(\"Waiting for file {}\\n\".format(waiting_for_file))\n            sys.stderr.flush()\n            time.sleep(1)\n\n    except KeyboardInterrupt:\n        sys.stderr.write(\"keyboard int received\\n\")\n        sys.stderr.flush()\n\n    finally:\n        sys.stderr.write(\"waiter cleaning up\\n\")\n        sys.stderr.flush()\n\n        child.terminate()\n        if cleanup_wait_time > 0:\n            time.sleep(cleanup_wait_time)\n\n        sys.stderr.write(\"waiter cleanup complete\\n\")\n        sys.stderr.flush()\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "testprojects/src/python/coordinated_runs/waiter.py"}]}