codecov[bot]: # [Codecov](https://codecov.io/gh/python-trio/trio/pull/1527?src=pr&el=h1) Report
> Merging [#1527](https://codecov.io/gh/python-trio/trio/pull/1527?src=pr&el=desc) into [master](https://codecov.io/gh/python-trio/trio/commit/72d790cc372d9ccb53cb49af3908c0e34429e1ea&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##           master    #1527      +/-   ##
==========================================
- Coverage   99.67%   99.65%   -0.03%     
==========================================
  Files         107      107              
  Lines       13239    13241       +2     
  Branches     1006     1007       +1     
==========================================
- Hits        13196    13195       -1     
- Misses         28       30       +2     
- Partials       15       16       +1     
```

| [Impacted Files](https://codecov.io/gh/python-trio/trio/pull/1527?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [trio/\_signals.py](https://codecov.io/gh/python-trio/trio/pull/1527/diff?src=pr&el=tree#diff-dHJpby9fc2lnbmFscy5weQ==) | `96.72% <0.00%> (-3.28%)` | :arrow_down: |
| [trio/\_unix\_pipes.py](https://codecov.io/gh/python-trio/trio/pull/1527/diff?src=pr&el=tree#diff-dHJpby9fdW5peF9waXBlcy5weQ==) | `98.86% <0.00%> (-1.14%)` | :arrow_down: |

kaldown: Finally, I won that `*.rst`.Tthis `__` at the end of the url, looks weird, but helpful.
oremanj: Also, please write "Closes #1526" in the PR description so that merging the PR will close the issue. Just listing the issue number isn't sufficient for that.
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 2a32d6c9..909ccf1b 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -9,6 +9,3 @@ python:
   version: 3.7
   install:
     - requirements: docs-requirements.txt
-
-sphinx:
-  fail_on_warning: true
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 49afad6c..9ec78d6e 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -24,18 +24,6 @@ sys.path.insert(0, os.path.abspath('.'))
 # For trio itself
 sys.path.insert(0, os.path.abspath('../..'))
 
-# https://docs.readthedocs.io/en/stable/builds.html#build-environment
-if "READTHEDOCS" in os.environ:
-    import glob
-    if glob.glob("../../newsfragments/*.*.rst"):
-        print("-- Found newsfragments; running towncrier --", flush=True)
-        import subprocess
-        subprocess.run(
-            ["towncrier", "--yes", "--date", "not released yet"],
-            cwd="../..",
-            check=True,
-        )
-
 # Warn about all references to unknown targets
 nitpicky = True
 # Except for these ones, which we expect to point to unknown targets:
@@ -49,7 +37,6 @@ nitpick_ignore = [
     ("py:mod", "trio.abc"),
     ("py:class", "math.inf"),
     ("py:exc", "Anything else"),
-    ("py:class", "async function"),
 ]
 autodoc_inherit_docstrings = False
 default_role = "obj"
diff --git a/newsfragments/1104.feature.rst b/newsfragments/1104.feature.rst
deleted file mode 100644
index 034fad25..00000000
--- a/newsfragments/1104.feature.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Previously, when `trio.run_process` was cancelled, it always killed
-the subprocess immediately. Now, on Unix, it first gives the process a
-chance to clean up by sending ``SIGTERM``, and only escalates to
-``SIGKILL`` if the process is still running after 5 seconds. But if
-you prefer the old behavior, or want to adjust the timeout, then don't
-worry: you can now pass a custom ``deliver_cancel=`` argument to
-define your own process killing policy.
diff --git a/newsfragments/1526.bugfix.rst b/newsfragments/1526.bugfix.rst
new file mode 100644
index 00000000..02e1d37c
--- /dev/null
+++ b/newsfragments/1526.bugfix.rst
@@ -0,0 +1,1 @@
+Calling `open_signal_receiver` with no arguments used to succeed without listening for any signals. This was confusing, so now it raises TypeError instead.
diff --git a/trio/_signals.py b/trio/_signals.py
index aca23dda..cee3b7db 100644
--- a/trio/_signals.py
+++ b/trio/_signals.py
@@ -129,6 +129,8 @@ def open_signal_receiver(*signals):
       signals: the signals to listen for.
 
     Raises:
+      TypeError: if no signals were provided.
+
       RuntimeError: if you try to use this anywhere except Python's main
           thread. (This is a Python limitation.)
 
@@ -144,6 +146,9 @@ def open_signal_receiver(*signals):
                  reload_configuration()
 
     """
+    if not signals:
+        raise TypeError("No signals were provided")
+
     if not is_main_thread():
         raise RuntimeError(
             "Sorry, open_signal_receiver is only possible when running in "
diff --git a/trio/_subprocess.py b/trio/_subprocess.py
index 0b656c71..ab6ab76a 100644
--- a/trio/_subprocess.py
+++ b/trio/_subprocess.py
@@ -3,7 +3,6 @@ import subprocess
 import sys
 from typing import Optional
 from functools import partial
-import warnings
 
 from ._abc import AsyncResource, SendStream, ReceiveStream
 from ._highlevel_generic import StapledStream
@@ -378,34 +377,6 @@ async def open_process(
     return Process._create(popen, trio_stdin, trio_stdout, trio_stderr)
 
 
-async def _windows_deliver_cancel(p):
-    try:
-        p.terminate()
-    except OSError as exc:
-        warnings.warn(
-            RuntimeWarning(f"TerminateProcess on {p!r} failed with: {exc!r}")
-        )
-
-
-async def _posix_deliver_cancel(p):
-    try:
-        p.terminate()
-        await trio.sleep(5)
-        warnings.warn(
-            RuntimeWarning(
-                f"process {p!r} ignored SIGTERM for 5 seconds. "
-                f"(Maybe you should custom deliver_cancel?) Trying SIGKILL."
-            )
-        )
-        p.kill()
-    except OSError as exc:
-        warnings.warn(
-            RuntimeWarning(
-                f"tried to kill process {p!r}, but failed with: {exc!r}"
-            )
-        )
-
-
 async def run_process(
     command,
     *,
@@ -413,7 +384,6 @@ async def run_process(
     capture_stdout=False,
     capture_stderr=False,
     check=True,
-    deliver_cancel=None,
     **options
 ):
     """Run ``command`` in a subprocess, wait for it to complete, and
@@ -471,7 +441,6 @@ async def run_process(
           ``**options``, or on Windows, ``command`` may alternatively
           be a string, which will be parsed following platform-dependent
           :ref:`quoting rules <subprocess-quoting>`.
-
       stdin (:obj:`bytes`, file descriptor, or None): The bytes to provide to
           the subprocess on its standard input stream, or ``None`` if the
           subprocess's standard input should come from the same place as
@@ -480,52 +449,18 @@ async def run_process(
           file descriptor or an object with a ``fileno()`` method,
           in which case the subprocess's standard input will come from
           that file.
-
       capture_stdout (bool): If true, capture the bytes that the subprocess
           writes to its standard output stream and return them in the
           :attr:`~subprocess.CompletedProcess.stdout` attribute
           of the returned :class:`~subprocess.CompletedProcess` object.
-
       capture_stderr (bool): If true, capture the bytes that the subprocess
           writes to its standard error stream and return them in the
           :attr:`~subprocess.CompletedProcess.stderr` attribute
           of the returned :class:`~subprocess.CompletedProcess` object.
-
       check (bool): If false, don't validate that the subprocess exits
           successfully. You should be sure to check the
           ``returncode`` attribute of the returned object if you pass
           ``check=False``, so that errors don't pass silently.
-
-      deliver_cancel (async function or None): If `run_process` is cancelled,
-          then it needs to kill the child process. There are multiple ways to
-          do this, so we let you customize it.
-
-          If you pass None (the default), then the behavior depends on the
-          platform:
-
-          - On Windows, Trio calls ``TerminateProcess``, which should kill the
-            process immediately.
-
-          - On Unix-likes, the default behavior is to send a ``SIGTERM``, wait
-            5 seconds, and send a ``SIGKILL``.
-
-          Alternatively, you can customize this behavior by passing in an
-          arbitrary async function, which will be called with the `Process`
-          object as an argument. For example, the default Unix behavior could
-          be implemented like this::
-
-             async def my_deliver_cancel(process):
-                 process.send_signal(signal.SIGTERM)
-                 await trio.sleep(5)
-                 process.send_signal(signal.SIGKILL)
-
-          When the process actually exits, the ``deliver_cancel`` function
-          will automatically be cancelled – so if the process exits after
-          ``SIGTERM``, then we'll never reach the ``SIGKILL``.
-
-          In any case, `run_process` will always wait for the child process to
-          exit before raising `Cancelled`.
-
       **options: :func:`run_process` also accepts any :ref:`general subprocess
           options <subprocess-options>` and passes them on to the
           :class:`~trio.Process` constructor. This includes the
@@ -583,13 +518,6 @@ async def run_process(
             raise ValueError("can't specify both stderr and capture_stderr")
         options["stderr"] = subprocess.PIPE
 
-    if deliver_cancel is None:
-        if os.name == "nt":
-            deliver_cancel = _windows_deliver_cancel
-        else:
-            assert os.name == "posix"
-            deliver_cancel = _posix_deliver_cancel
-
     stdout_chunks = []
     stderr_chunks = []
 
@@ -614,20 +542,7 @@ async def run_process(
                 nursery.start_soon(read_output, proc.stdout, stdout_chunks)
             if proc.stderr is not None:
                 nursery.start_soon(read_output, proc.stderr, stderr_chunks)
-            try:
-                await proc.wait()
-            except trio.Cancelled:
-                with trio.CancelScope(shield=True):
-                    killer_cscope = trio.CancelScope(shield=True)
-
-                    async def killer():
-                        with killer_cscope:
-                            await deliver_cancel(proc)
-
-                    nursery.start_soon(killer)
-                    await proc.wait()
-                    killer_cscope.cancel()
-                    raise
+            await proc.wait()
 
     stdout = b"".join(stdout_chunks) if proc.stdout is not None else None
     stderr = b"".join(stderr_chunks) if proc.stderr is not None else None

trio.open_signal_receiver should give an error if no arguments are passed
User confusion spotted in the wild: https://gitter.im/python-trio/general?at=5ebfa9d613878c30b581b9fe

diff --git a/trio/tests/test_signals.py b/trio/tests/test_signals.py
index 7ae93040..20821b40 100644
--- a/trio/tests/test_signals.py
+++ b/trio/tests/test_signals.py
@@ -41,6 +41,12 @@ async def test_open_signal_receiver_restore_handler_after_one_bad_signal():
     assert signal.getsignal(signal.SIGILL) is orig
 
 
+async def test_open_signal_receiver_empty_fail():
+    with pytest.raises(TypeError, match="No signals were provided"):
+        with open_signal_receiver():
+            pass
+
+
 async def test_open_signal_receiver_restore_handler_after_duplicate_signal():
     orig = signal.getsignal(signal.SIGILL)
     with open_signal_receiver(signal.SIGILL, signal.SIGILL):
diff --git a/trio/tests/test_subprocess.py b/trio/tests/test_subprocess.py
index c5be0e39..ebc838b0 100644
--- a/trio/tests/test_subprocess.py
+++ b/trio/tests/test_subprocess.py
@@ -4,7 +4,6 @@ import subprocess
 import sys
 import pytest
 import random
-from functools import partial
 
 from .. import (
     _core, move_on_after, fail_after, sleep, sleep_forever, Process,
@@ -426,56 +425,3 @@ def test_waitid_eintr():
             sleeper.kill()
             sleeper.wait()
         signal.signal(signal.SIGALRM, old_sigalrm)
-
-
-async def test_custom_deliver_cancel():
-    custom_deliver_cancel_called = False
-
-    async def custom_deliver_cancel(proc):
-        nonlocal custom_deliver_cancel_called
-        custom_deliver_cancel_called = True
-        proc.terminate()
-        # Make sure this does get cancelled when the process exits, and that
-        # the process really exited.
-        try:
-            await sleep_forever()
-        finally:
-            assert proc.returncode is not None
-
-    async with _core.open_nursery() as nursery:
-        nursery.start_soon(
-            partial(
-                run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel
-            )
-        )
-        await wait_all_tasks_blocked()
-        nursery.cancel_scope.cancel()
-
-    assert custom_deliver_cancel_called
-
-
-async def test_warn_on_failed_cancel_terminate(monkeypatch):
-    original_terminate = Process.terminate
-
-    def broken_terminate(self):
-        original_terminate(self)
-        raise OSError("whoops")
-
-    monkeypatch.setattr(Process, "terminate", broken_terminate)
-
-    with pytest.warns(RuntimeWarning, match=".*whoops.*"):
-        async with _core.open_nursery() as nursery:
-            nursery.start_soon(run_process, SLEEP(9999))
-            await wait_all_tasks_blocked()
-            nursery.cancel_scope.cancel()
-
-
-@pytest.mark.skipif(os.name != "posix", reason="posix only")
-async def test_warn_on_cancel_SIGKILL_escalation(autojump_clock, monkeypatch):
-    monkeypatch.setattr(Process, "terminate", lambda *args: None)
-
-    with pytest.warns(RuntimeWarning, match=".*ignored SIGTERM.*"):
-        async with _core.open_nursery() as nursery:
-            nursery.start_soon(run_process, SLEEP(9999))
-            await wait_all_tasks_blocked()
-            nursery.cancel_scope.cancel()

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
async-generator==1.10
attrs==25.3.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
idna==3.10
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
outcome==1.3.0.post0
packaging @ file:///croot/packaging_1720101850331/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042571233/work
pytest @ file:///croot/pytest_1717793244625/work
sniffio==1.3.1
sortedcontainers==2.4.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
-e git+https://github.com/python-trio/trio.git@fa4ded772e3bc0b26e85c4e5deffabad1b257c01#egg=trio

name: trio
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py38h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.1=py38h06a4308_0
  - pip=24.2=py38h06a4308_0
  - pluggy=1.0.0=py38h06a4308_1
  - pytest=7.4.4=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py38h06a4308_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - async-generator==1.10
      - attrs==25.3.0
      - idna==3.10
      - outcome==1.3.0.post0
      - sniffio==1.3.1
      - sortedcontainers==2.4.0
      - trio==0.14.0+dev
prefix: /opt/conda/envs/trio

trio/tests/test_signals.py::test_open_signal_receiver_empty_fail
trio/tests/test_signals.py::test_open_signal_receiver trio/tests/test_signals.py::test_open_signal_receiver_restore_handler_after_one_bad_signal trio/tests/test_signals.py::test_open_signal_receiver_restore_handler_after_duplicate_signal trio/tests/test_signals.py::test_catch_signals_wrong_thread trio/tests/test_signals.py::test_open_signal_receiver_conflict trio/tests/test_signals.py::test_open_signal_receiver_no_starvation trio/tests/test_signals.py::test_catch_signals_race_condition_on_exit trio/tests/test_subprocess.py::test_basic trio/tests/test_subprocess.py::test_auto_update_returncode trio/tests/test_subprocess.py::test_multi_wait trio/tests/test_subprocess.py::test_kill_when_context_cancelled trio/tests/test_subprocess.py::test_pipes trio/tests/test_subprocess.py::test_interactive trio/tests/test_subprocess.py::test_run trio/tests/test_subprocess.py::test_run_check trio/tests/test_subprocess.py::test_run_with_broken_pipe trio/tests/test_subprocess.py::test_stderr_stdout trio/tests/test_subprocess.py::test_errors trio/tests/test_subprocess.py::test_signals trio/tests/test_subprocess.py::test_wait_reapable_fails
trio/_subprocess.py newsfragments/1104.feature.rst docs/source/conf.py .readthedocs.yml newsfragments/1526.bugfix.rst trio/_signals.py
trio/_subprocess.py newsfragments/1104.feature.rst docs/source/conf.py .readthedocs.yml newsfragments/1526.bugfix.rst trio/_signals.py