From 45f85dad44585d8405c580c7640d47662fccc424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 15 Jan 2022 19:47:15 +0200 Subject: [PATCH 01/12] Drop Python 3.6 support --- .github/workflows/ci.yml | 8 ++++---- README.rst | 6 +++--- docs/source/index.rst | 2 +- docs/source/tutorial.rst | 5 +---- pyproject.toml | 2 +- setup.py | 6 +++--- trio/_core/_wakeup_socketpair.py | 3 --- trio/_core/tests/test_guest_mode.py | 2 +- trio/_core/tests/test_ki.py | 7 ------- trio/_core/tests/test_multierror.py | 6 +----- trio/tests/test_ssl.py | 5 +---- 11 files changed, 16 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 606e64977b..65fa899179 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python: ['3.7', '3.8', '3.9', '3.10'] arch: ['x86', 'x64'] lsp: [''] lsp_extract_file: [''] @@ -51,7 +51,7 @@ jobs: # This avoids the need to update for each new alpha, beta, release candidate, # and then finally an actual release version. actions/setup-python doesn't # support this for PyPy presently so we get no help there. - # + # # CPython -> 3.9.0-alpha - 3.9.X # PyPy -> pypy-3.7 python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} @@ -72,7 +72,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['pypy-3.6', 'pypy-3.7', 'pypy-3.8', '3.6', '3.7', '3.8', '3.9', '3.10', '3.8-dev', '3.9-dev', '3.10-dev'] + python: ['pypy-3.7', 'pypy-3.8', '3.7', '3.8', '3.9', '3.10', '3.8-dev', '3.9-dev', '3.10-dev'] check_formatting: ['0'] pypy_nightly_branch: [''] extra_name: [''] @@ -114,7 +114,7 @@ jobs: strategy: fail-fast: false matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python: ['3.7', '3.8', '3.9', '3.10'] include: - python: '3.8' # <- not actually used arch: 'x64' diff --git a/README.rst b/README.rst index c4b84c1711..4e096eddf3 100644 --- a/README.rst +++ b/README.rst @@ -9,14 +9,14 @@ .. image:: https://img.shields.io/badge/docs-read%20now-blue.svg :target: https://trio.readthedocs.io :alt: Documentation - + .. image:: https://img.shields.io/pypi/v/trio.svg :target: https://pypi.org/project/trio :alt: Latest PyPi version .. image:: https://img.shields.io/conda/vn/conda-forge/trio.svg :target: https://anaconda.org/conda-forge/trio - :alt: Latest conda-forge version + :alt: Latest conda-forge version .. image:: https://codecov.io/gh/python-trio/trio/branch/master/graph/badge.svg :target: https://codecov.io/gh/python-trio/trio @@ -92,7 +92,7 @@ demonstration of implementing the "Happy Eyeballs" algorithm in an older library versus Trio. **Cool, but will it work on my system?** Probably! As long as you have -some kind of Python 3.6-or-better (CPython or the latest PyPy3 are +some kind of Python 3.7-or-better (CPython or the latest PyPy3 are both fine), and are using Linux, macOS, Windows, or FreeBSD, then Trio will work. Other environments might work too, but those are the ones we test on. And all of our dependencies are pure Python, diff --git a/docs/source/index.rst b/docs/source/index.rst index e3adddaee8..84d81880af 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -45,7 +45,7 @@ Vital statistics: * Supported environments: We test on - - Python: 3.6+ (CPython and PyPy) + - Python: 3.7+ (CPython and PyPy) - Windows, macOS, Linux (glibc and musl), FreeBSD Other environments might also work; give it a try and see. diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index aedba8244a..08206569f5 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -34,9 +34,6 @@ Tutorial print(response) and then again with /delay/10 - (note that asks needs cpython 3.6 though. maybe just for one async - generator?) - value of async/await: show you where the cancellation exceptions can happen -- see pillar re: explicit cancel points @@ -94,7 +91,7 @@ Okay, ready? Let's get started. Before you begin ---------------- -1. Make sure you're using Python 3.6 or newer. +1. Make sure you're using Python 3.7 or newer. 2. ``python3 -m pip install --upgrade trio`` (or on Windows, maybe ``py -3 -m pip install --upgrade trio`` – `details diff --git a/pyproject.toml b/pyproject.toml index 1378e5df7b..944440661f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.black] -target-version = ['py36'] +target-version = ['py37'] [tool.towncrier] diff --git a/setup.py b/setup.py index 11eda8e96f..a832efcd85 100644 --- a/setup.py +++ b/setup.py @@ -89,12 +89,11 @@ # cffi 1.14 fixes memory leak inside ffi.getwinerror() # cffi is required on Windows, except on PyPy where it is built-in "cffi>=1.14; os_name == 'nt' and implementation_name != 'pypy'", - "contextvars>=2.1; python_version < '3.7'", ], # This means, just install *everything* you see under trio/, even if it # doesn't look like a source file, so long as it appears in MANIFEST.in: include_package_data=True, - python_requires=">=3.6", + python_requires=">=3.7", keywords=["async", "io", "networking", "trio"], classifiers=[ "Development Status :: 3 - Alpha", @@ -108,10 +107,11 @@ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: System :: Networking", "Framework :: Trio", ], diff --git a/trio/_core/_wakeup_socketpair.py b/trio/_core/_wakeup_socketpair.py index 121cec584e..8115eba3db 100644 --- a/trio/_core/_wakeup_socketpair.py +++ b/trio/_core/_wakeup_socketpair.py @@ -8,9 +8,6 @@ def _has_warn_on_full_buffer(): - if sys.version_info < (3, 7): - return False - if "__pypy__" not in sys.builtin_module_names: # CPython has warn_on_full_buffer. Don't need to inspect. # Also, CPython doesn't support inspecting built-in functions. diff --git a/trio/_core/tests/test_guest_mode.py b/trio/_core/tests/test_guest_mode.py index b06849a836..564b65d88a 100644 --- a/trio/_core/tests/test_guest_mode.py +++ b/trio/_core/tests/test_guest_mode.py @@ -504,7 +504,7 @@ async def trio_main(in_host): @pytest.mark.skipif(buggy_pypy_asyncgens, reason="PyPy 7.2 is buggy") @pytest.mark.xfail( - sys.implementation.name == "pypy" and sys.version_info >= (3, 7), + sys.implementation.name == "pypy", reason="async generator issue under investigation", ) @restore_unraisablehook() diff --git a/trio/_core/tests/test_ki.py b/trio/_core/tests/test_ki.py index 0e4db4af49..8962003c84 100644 --- a/trio/_core/tests/test_ki.py +++ b/trio/_core/tests/test_ki.py @@ -543,10 +543,6 @@ def test_ki_wakes_us_up(): # https://bitbucket.org/pypy/pypy/issues/2623 import platform - buggy_wakeup_fd = ( - sys.version_info < (3, 6, 2) and platform.python_implementation() == "CPython" - ) - # lock is only needed to avoid an annoying race condition where the # *second* ki_self() call arrives *after* the first one woke us up and its # KeyboardInterrupt was caught, and then generates a second @@ -571,9 +567,6 @@ def kill_soon(): with lock: print("thread doing ki_self()") ki_self() - if buggy_wakeup_fd: - print("buggy_wakeup_fd =", buggy_wakeup_fd) - ki_self() async def main(): thread = threading.Thread(target=kill_soon) diff --git a/trio/_core/tests/test_multierror.py b/trio/_core/tests/test_multierror.py index 70d763e652..858d1652ae 100644 --- a/trio/_core/tests/test_multierror.py +++ b/trio/_core/tests/test_multierror.py @@ -164,11 +164,7 @@ def test_traceback_recursion(): exc1.__cause__ = MultiError([exc1, exc2, exc3]) # python traceback.TracebackException < 3.6.4 does not support unhashable exceptions # and raises a TypeError exception - if sys.version_info < (3, 6, 4): - with pytest.raises(TypeError): - format_exception(*einfo(exc1)) - else: - format_exception(*einfo(exc1)) + format_exception(*einfo(exc1)) def make_tree(): diff --git a/trio/tests/test_ssl.py b/trio/tests/test_ssl.py index f3bff42195..907d04b809 100644 --- a/trio/tests/test_ssl.py +++ b/trio/tests/test_ssl.py @@ -87,10 +87,7 @@ def client_ctx(request): if request.param in ["default", "tls13"]: return ctx elif request.param == "tls12": - if sys.version_info >= (3, 7): - ctx.maximum_version = ssl.TLSVersion.TLSv1_2 - else: - ctx.options |= ssl.OP_NO_TLSv1_3 + ctx.maximum_version = ssl.TLSVersion.TLSv1_2 return ctx else: # pragma: no cover assert False From 5ca92641c3a45c940b8dbc8615ad3dfe04ecf424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 01:05:44 +0200 Subject: [PATCH 02/12] Removed Python 3.6 IPython hack in CI script --- ci.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ci.sh b/ci.sh index b4f56f7114..d4f9df3a94 100755 --- a/ci.sh +++ b/ci.sh @@ -72,14 +72,6 @@ python -m pip --version python setup.py sdist --formats=zip python -m pip install dist/*.zip -if python -c 'import sys; sys.exit(sys.version_info >= (3, 7))'; then - # Python < 3.7, select last ipython with 3.6 support - # macOS requires the suffix for --in-place or you get an undefined label error - sed -i'.bak' 's/ipython==[^ ]*/ipython==7.16.1/' test-requirements.txt - sed -i'.bak' 's/traitlets==[^ ]*/traitlets==4.3.3/' test-requirements.txt - git diff test-requirements.txt -fi - if [ "$CHECK_FORMATTING" = "1" ]; then python -m pip install -r test-requirements.txt source check.sh From 7a1b18600d31cc3642c8d66df5af67505e1e21a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 12:56:38 +0200 Subject: [PATCH 03/12] Removed exc_key compatibility hack in MultiError --- trio/_core/_multierror.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index b6bf2cf727..514f8764c8 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -5,13 +5,6 @@ import attr -# python traceback.TracebackException < 3.6.4 does not support unhashable exceptions -# see https://github.com/python/cpython/pull/4014 for details -if sys.version_info < (3, 6, 4): - exc_key = lambda exc: exc -else: - exc_key = id - ################################################################ # MultiError ################################################################ @@ -419,7 +412,7 @@ def traceback_exception_init( if isinstance(exc_value, MultiError): embedded = [] for exc in exc_value.exceptions: - if exc_key(exc) not in _seen: + if id(exc) not in _seen: embedded.append( traceback.TracebackException.from_exception( exc, From c023ad0567fbd589c88b2076e34d9670717aa359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 12:59:27 +0200 Subject: [PATCH 04/12] Removed obsolete frame hiding trick --- trio/_core/_run.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/trio/_core/_run.py b/trio/_core/_run.py index fcb36c32c5..4ca3e9f313 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -1,18 +1,12 @@ -# coding: utf-8 - import functools import itertools -import logging -import os import random import select import sys import threading from collections import deque -import collections.abc from contextlib import contextmanager import warnings -import weakref import enum from contextvars import copy_context @@ -47,7 +41,6 @@ from ._thread_cache import start_thread_soon from ._instrumentation import Instruments from .. import _core -from .._deprecate import warn_deprecated from .._util import Final, NoPublicConstructor, coroutine_or_error DEADLINE_HEAP_MIN_PRUNE_THRESHOLD = 1000 @@ -70,34 +63,6 @@ def _public(fn): _r = random.Random() -# On 3.7+, Context.run() is implemented in C and doesn't show up in -# tracebacks. On 3.6, we use the contextvars backport, which is -# currently implemented in Python and adds 1 frame to tracebacks. So this -# function is a super-overkill version of "0 if sys.version_info >= (3, 7) -# else 1". But if Context.run ever changes, we'll be ready! -# -# This can all be removed once we drop support for 3.6. -def _count_context_run_tb_frames(): - def function_with_unique_name_xyzzy(): - 1 / 0 - - ctx = copy_context() - try: - ctx.run(function_with_unique_name_xyzzy) - except ZeroDivisionError as exc: - tb = exc.__traceback__ - # Skip the frame where we caught it - tb = tb.tb_next - count = 0 - while tb.tb_frame.f_code.co_name != "function_with_unique_name_xyzzy": - tb = tb.tb_next - count += 1 - return count - - -CONTEXT_RUN_TB_FRAMES = _count_context_run_tb_frames() - - @attr.s(frozen=True, slots=True) class SystemClock: # Add a large random offset to our clock to ensure that if people @@ -2186,8 +2151,6 @@ def unrolled_run(runner, async_fn, args, host_uses_signal_set_wakeup_fd=False): # catching it, and then in addition we remove however many # more Context.run adds. tb = task_exc.__traceback__.tb_next - for _ in range(CONTEXT_RUN_TB_FRAMES): - tb = tb.tb_next final_outcome = Error(task_exc.with_traceback(tb)) # Remove local refs so that e.g. cancelled coroutine locals # are not kept alive by this frame until another exception From d130f120247d760156a4ff715c2b9190981ffe88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 13:02:36 +0200 Subject: [PATCH 05/12] Removed KI test that is no longer required --- trio/_core/tests/test_ki.py | 112 ------------------------------------ 1 file changed, 112 deletions(-) diff --git a/trio/_core/tests/test_ki.py b/trio/_core/tests/test_ki.py index 8962003c84..430352ab6d 100644 --- a/trio/_core/tests/test_ki.py +++ b/trio/_core/tests/test_ki.py @@ -499,115 +499,3 @@ async def inner(): _core.run(inner) finally: threading._active[thread.ident] = original - - -# For details on why this test is non-trivial, see: -# https://github.com/python-trio/trio/issues/42 -# https://github.com/python-trio/trio/issues/109 -@slow -def test_ki_wakes_us_up(): - assert is_main_thread() - - # This test is flaky due to a race condition on Windows; see: - # https://github.com/python-trio/trio/issues/119 - # https://bugs.python.org/issue30038 - # I think the only fix is to wait for fixed CPython to be released, so in - # the mean time, on affected versions we send two signals (equivalent to - # hitting control-C twice). This works because the problem is that the C - # level signal handler does - # - # write-to-fd -> set-flags - # - # and we need - # - # set-flags -> write-to-fd - # - # so running the C level signal handler twice does - # - # write-to-fd -> set-flags -> write-to-fd -> set-flags - # - # which contains the desired sequence. - # - # Affected version of CPython include 3.6.1 and earlier. - # It's fixed in 3.6.2 and 3.7+ - # - # PyPy was never affected. - # - # The problem technically can occur on Unix as well, if a signal is - # delivered to a non-main thread, though we haven't observed this in - # practice. - # - # There's also this theoretical problem, but hopefully it won't actually - # bite us in practice: - # https://bugs.python.org/issue31119 - # https://bitbucket.org/pypy/pypy/issues/2623 - import platform - - # lock is only needed to avoid an annoying race condition where the - # *second* ki_self() call arrives *after* the first one woke us up and its - # KeyboardInterrupt was caught, and then generates a second - # KeyboardInterrupt that aborts the test run. The kill_soon thread holds - # the lock while doing the calls to ki_self, which means that it holds it - # while the C-level signal handler is running. Then in the main thread, - # when we're woken up we know that ki_self() has been run at least once; - # if we then take the lock it guaranteeds that ki_self() has been run - # twice, so if a second KeyboardInterrupt is going to arrive it should - # arrive by the time we've acquired the lock. This lets us force it to - # happen inside the pytest.raises block. - # - # It will be very nice when the buggy_wakeup_fd bug is fixed. - lock = threading.Lock() - - def kill_soon(): - # We want the signal to be raised after the main thread has entered - # the IO manager blocking primitive. There really is no way to - # deterministically interlock with that, so we have to use sleep and - # hope it's long enough. - time.sleep(1.1) - with lock: - print("thread doing ki_self()") - ki_self() - - async def main(): - thread = threading.Thread(target=kill_soon) - print("Starting thread") - thread.start() - try: - with pytest.raises(KeyboardInterrupt): - # To limit the damage on CI if this does get broken (as - # compared to sleep_forever()) - print("Going to sleep") - try: - await sleep(20) - print("Woke without raising?!") # pragma: no cover - # The only purpose of this finally: block is to soak up the - # second KeyboardInterrupt that might arrive on - # buggy_wakeup_fd platforms. So it might get aborted at any - # moment randomly on some runs, so pragma: no cover avoids - # coverage flapping: - finally: # pragma: no cover - print("waiting for lock") - with lock: - print("got lock") - # And then we want to force a PyErr_CheckSignals. Which is - # not so easy on Windows. Weird kluge: builtin_repr calls - # PyObject_Repr, which does an unconditional - # PyErr_CheckSignals for some reason. - print(repr(None)) - # And finally, it's possible that the signal was delivered - # but at a moment when we had KI protection enabled, so we - # need to execute a checkpoint to ensure it's delivered - # before we exit main(). - await _core.checkpoint() - finally: - print("joining thread", sys.exc_info()) - thread.join() - - start = time.perf_counter() - try: - _core.run(main) - finally: - end = time.perf_counter() - print("duration", end - start) - print("sys.exc_info", sys.exc_info()) - assert 1.0 <= (end - start) < 2 From 8ffc1b19ad80d542e8ffb8a11a2e17d73f018c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 17:50:32 +0200 Subject: [PATCH 06/12] Updated a number of comments referring to Python 3.6 --- docs/source/reference-io.rst | 5 ++--- setup.py | 2 +- trio/_core/_entry_queue.py | 4 ++-- trio/_core/_run.py | 2 +- trio/_core/tests/test_multierror.py | 2 -- trio/_core/tests/test_run.py | 2 +- trio/_highlevel_ssl_helpers.py | 1 - trio/_socket.py | 2 +- trio/_util.py | 13 ++++++------- trio/tests/test_threads.py | 4 ++-- 10 files changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/source/reference-io.rst b/docs/source/reference-io.rst index 4d33601f17..b18983e272 100644 --- a/docs/source/reference-io.rst +++ b/docs/source/reference-io.rst @@ -237,8 +237,7 @@ other constants and functions in the :mod:`ssl` module. .. warning:: Avoid instantiating :class:`ssl.SSLContext` directly. A newly constructed :class:`~ssl.SSLContext` has less secure - defaults than one returned by :func:`ssl.create_default_context`, - dramatically so before Python 3.6. + defaults than one returned by :func:`ssl.create_default_context`. Instead of using :meth:`ssl.SSLContext.wrap_socket`, you create a :class:`SSLStream`: @@ -722,7 +721,7 @@ subprocess`` in order to access constants such as ``PIPE`` or Currently, Trio always uses unbuffered byte streams for communicating with a process, so it does not support the ``encoding``, ``errors``, -``universal_newlines`` (alias ``text`` in 3.7+), and ``bufsize`` +``universal_newlines`` (alias ``text``), and ``bufsize`` options. diff --git a/setup.py b/setup.py index a832efcd85..732fa7acd7 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ Vital statistics: * Supported environments: Linux, macOS, or Windows running some kind of Python - 3.6-or-better (either CPython or PyPy3 is fine). \\*BSD and illumos likely + 3.7-or-better (either CPython or PyPy3 is fine). \\*BSD and illumos likely work too, but are not tested. * Install: ``python3 -m pip install -U trio`` (or on Windows, maybe diff --git a/trio/_core/_entry_queue.py b/trio/_core/_entry_queue.py index 8f6eb05e3b..9f3301b3d2 100644 --- a/trio/_core/_entry_queue.py +++ b/trio/_core/_entry_queue.py @@ -15,8 +15,8 @@ class EntryQueue: # not signal-safe. deque is implemented in C, so each operation is atomic # WRT threads (and this is guaranteed in the docs), AND each operation is # atomic WRT signal delivery (signal handlers can run on either side, but - # not *during* a deque operation). dict makes similar guarantees - and on - # CPython 3.6 and PyPy, it's even ordered! + # not *during* a deque operation). dict makes similar guarantees - and + # it's even ordered! queue = attr.ib(factory=deque) idempotent_queue = attr.ib(factory=dict) diff --git a/trio/_core/_run.py b/trio/_core/_run.py index 4ca3e9f313..8ab0ce7da3 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -2137,7 +2137,7 @@ def unrolled_run(runner, async_fn, args, host_uses_signal_set_wakeup_fd=False): try: # We used to unwrap the Outcome object here and send/throw # its contents in directly, but it turns out that .throw() - # is buggy, at least on CPython 3.6: + # is buggy, at least before CPython 3.9: # https://bugs.python.org/issue29587 # https://bugs.python.org/issue29590 # So now we send in the Outcome object and unwrap it on the diff --git a/trio/_core/tests/test_multierror.py b/trio/_core/tests/test_multierror.py index 858d1652ae..e4a30e0e69 100644 --- a/trio/_core/tests/test_multierror.py +++ b/trio/_core/tests/test_multierror.py @@ -162,8 +162,6 @@ def test_traceback_recursion(): # This could trigger an infinite recursion; the 'seen' set is supposed to prevent # this. exc1.__cause__ = MultiError([exc1, exc2, exc3]) - # python traceback.TracebackException < 3.6.4 does not support unhashable exceptions - # and raises a TypeError exception format_exception(*einfo(exc1)) diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py index a559ac11ed..863dad9909 100644 --- a/trio/_core/tests/test_run.py +++ b/trio/_core/tests/test_run.py @@ -1056,7 +1056,7 @@ async def child2(): ] -# At least as of CPython 3.6, using .throw() to raise an exception inside a +# Before CPython 3.9, using .throw() to raise an exception inside a # coroutine/generator causes the original exc_info state to be lost, so things # like re-raising and exception chaining are broken. # diff --git a/trio/_highlevel_ssl_helpers.py b/trio/_highlevel_ssl_helpers.py index a339a3d238..19b1ff8777 100644 --- a/trio/_highlevel_ssl_helpers.py +++ b/trio/_highlevel_ssl_helpers.py @@ -19,7 +19,6 @@ async def open_ssl_over_tcp_stream( *, https_compatible=False, ssl_context=None, - # No trailing comma b/c bpo-9232 (fixed in py36) happy_eyeballs_delay=DEFAULT_DELAY, ): """Make a TLS-encrypted Connection to the given host and port over TCP. diff --git a/trio/_socket.py b/trio/_socket.py index 4e4e603726..886f5614f6 100644 --- a/trio/_socket.py +++ b/trio/_socket.py @@ -50,7 +50,7 @@ async def __aexit__(self, etype, value, tb): try: from socket import IPPROTO_IPV6 except ImportError: - # As of at least 3.6, python on Windows is missing IPPROTO_IPV6 + # Before Python 3.8, Windows is missing IPPROTO_IPV6 # https://bugs.python.org/issue29515 if sys.platform == "win32": # pragma: no branch IPPROTO_IPV6 = 41 diff --git a/trio/_util.py b/trio/_util.py index ec0350b305..cf33ebbedd 100644 --- a/trio/_util.py +++ b/trio/_util.py @@ -270,13 +270,12 @@ def __getitem__(self, _): # If a new class inherits from any ABC, then the new class's metaclass has to -# inherit from ABCMeta. If a new class inherits from typing.Generic, and -# you're using Python 3.6, then the new class's metaclass has to -# inherit from typing.GenericMeta. Some of the classes that want to use Final -# or NoPublicConstructor inherit from ABCs and generics, so Final has to -# inherit from these metaclasses. Fortunately, GenericMeta inherits from -# ABCMeta, so inheriting from GenericMeta alone is sufficient (when it -# exists at all). +# inherit from ABCMeta. If a new class inherits from typing.Generic, then the +# new class's metaclass has to inherit from typing.GenericMeta. Some of the +# classes that want to use Final or NoPublicConstructor inherit from ABCs and +# generics, so Final has to inherit from these metaclasses. Fortunately, +# GenericMeta inherits from ABCMeta, so inheriting from GenericMeta alone is +# sufficient (when it exists at all). if not t.TYPE_CHECKING and hasattr(t, "GenericMeta"): BaseMeta = t.GenericMeta else: diff --git a/trio/tests/test_threads.py b/trio/tests/test_threads.py index 47727817e6..11fc34c131 100644 --- a/trio/tests/test_threads.py +++ b/trio/tests/test_threads.py @@ -300,8 +300,8 @@ async def test_run_in_worker_thread_limiter(MAX, cancel, use_default_limiter): try: # We used to use regular variables and 'nonlocal' here, but it turns # out that it's not safe to assign to closed-over variables that are - # visible in multiple threads, at least as of CPython 3.6 and PyPy - # 5.8: + # visible in multiple threads, at least as of CPython 3.10 and PyPy + # 7.3: # # https://bugs.python.org/issue30744 # https://bitbucket.org/pypy/pypy/issues/2591/ From d24c7d395f85542231904044be4bae9c9a88f46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 22 Jan 2022 18:23:58 +0200 Subject: [PATCH 07/12] Restored the frame hiding trick Turns out it this is still needed on PyPy. --- trio/_core/_run.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/trio/_core/_run.py b/trio/_core/_run.py index 8ab0ce7da3..f3adfc7580 100644 --- a/trio/_core/_run.py +++ b/trio/_core/_run.py @@ -63,6 +63,29 @@ def _public(fn): _r = random.Random() +# On CPython, Context.run() is implemented in C and doesn't show up in +# tracebacks. On PyPy, it is implemented in Python and adds 1 frame to tracebacks. +def _count_context_run_tb_frames(): + def function_with_unique_name_xyzzy(): + 1 / 0 + + ctx = copy_context() + try: + ctx.run(function_with_unique_name_xyzzy) + except ZeroDivisionError as exc: + tb = exc.__traceback__ + # Skip the frame where we caught it + tb = tb.tb_next + count = 0 + while tb.tb_frame.f_code.co_name != "function_with_unique_name_xyzzy": + tb = tb.tb_next + count += 1 + return count + + +CONTEXT_RUN_TB_FRAMES = _count_context_run_tb_frames() + + @attr.s(frozen=True, slots=True) class SystemClock: # Add a large random offset to our clock to ensure that if people @@ -2151,6 +2174,8 @@ def unrolled_run(runner, async_fn, args, host_uses_signal_set_wakeup_fd=False): # catching it, and then in addition we remove however many # more Context.run adds. tb = task_exc.__traceback__.tb_next + for _ in range(CONTEXT_RUN_TB_FRAMES): + tb = tb.tb_next final_outcome = Error(task_exc.with_traceback(tb)) # Remove local refs so that e.g. cancelled coroutine locals # are not kept alive by this frame until another exception From dff4c1fa9bb1403cae3b1af1fd488345c1bf120f Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 24 Jan 2022 11:03:43 +0400 Subject: [PATCH 08/12] Stop checking for warn_on_full_buffer PyPy added support in 7.3.4, the first release with Python 3.7 support that was not beta. --- trio/_core/_wakeup_socketpair.py | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/trio/_core/_wakeup_socketpair.py b/trio/_core/_wakeup_socketpair.py index 8115eba3db..8d51419ecc 100644 --- a/trio/_core/_wakeup_socketpair.py +++ b/trio/_core/_wakeup_socketpair.py @@ -1,5 +1,4 @@ import socket -import sys import signal import warnings @@ -7,21 +6,6 @@ from .._util import is_main_thread -def _has_warn_on_full_buffer(): - if "__pypy__" not in sys.builtin_module_names: - # CPython has warn_on_full_buffer. Don't need to inspect. - # Also, CPython doesn't support inspecting built-in functions. - return True - - import inspect - - args_spec = inspect.getfullargspec(signal.set_wakeup_fd) - return "warn_on_full_buffer" in args_spec.kwonlyargs - - -HAVE_WARN_ON_FULL_BUFFER = _has_warn_on_full_buffer() - - class WakeupSocketpair: def __init__(self): self.wakeup_sock, self.write_sock = socket.socketpair() @@ -35,13 +19,8 @@ def __init__(self): # Windows 10: 525347 # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send # blocking, even on non-blocking sockets, so don't do that.) - # - # But, if we're on an old Python and can't control the signal module's - # warn-on-full-buffer behavior, then we need to leave things alone, so - # the signal module won't spam the console with spurious warnings. - if HAVE_WARN_ON_FULL_BUFFER: - self.wakeup_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) - self.write_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) + self.wakeup_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) + self.write_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) # On Windows this is a TCP socket so this might matter. On other # platforms this fails b/c AF_UNIX sockets aren't actually TCP. try: @@ -72,10 +51,7 @@ def wakeup_on_signals(self): if not is_main_thread(): return fd = self.write_sock.fileno() - if HAVE_WARN_ON_FULL_BUFFER: - self.old_wakeup_fd = signal.set_wakeup_fd(fd, warn_on_full_buffer=False) - else: - self.old_wakeup_fd = signal.set_wakeup_fd(fd) + self.old_wakeup_fd = signal.set_wakeup_fd(fd, warn_on_full_buffer=False) if self.old_wakeup_fd != -1: warnings.warn( RuntimeWarning( From c16bcc3d4562f3491c1b7d2202ef23054090075f Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 24 Jan 2022 11:15:31 +0400 Subject: [PATCH 09/12] Stop installing contextvars since we dropped 3.6 support --- test-requirements.in | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test-requirements.in b/test-requirements.in index 894fc654d3..99854204d6 100644 --- a/test-requirements.in +++ b/test-requirements.in @@ -22,15 +22,9 @@ typing-extensions; implementation_name == "cpython" # Trio's own dependencies cffi; os_name == "nt" -contextvars; python_version < "3.7" attrs >= 19.2.0 sortedcontainers async_generator >= 1.9 idna outcome sniffio - -# Required by contextvars, but harmless to install everywhere. -# dependabot drops the contextvars dependency because it runs -# on 3.7. -immutables >= 0.6 From babc929daf313cd06a951e7194790433ed7b0465 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 24 Jan 2022 14:59:45 +0400 Subject: [PATCH 10/12] Remove TCP_NOTSENT_LOWAT definitions They always exist in Python 3.7+ --- trio/socket.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/trio/socket.py b/trio/socket.py index 27e75c8dbc..613375ef41 100644 --- a/trio/socket.py +++ b/trio/socket.py @@ -188,18 +188,6 @@ # get names used by Trio that we define on our own from ._socket import IPPROTO_IPV6 -# Not defined in all python versions and platforms but sometimes needed -if not _t.TYPE_CHECKING: - try: - TCP_NOTSENT_LOWAT - except NameError: - # Hopefully will show up in 3.7: - # https://github.com/python/cpython/pull/477 - if sys.platform == "darwin": - TCP_NOTSENT_LOWAT = 0x201 - elif sys.platform == "linux": - TCP_NOTSENT_LOWAT = 25 - if _t.TYPE_CHECKING: IP_BIND_ADDRESS_NO_PORT: int else: From a554b6bdcfe3a310799634bf485de0a26ed5ac4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 24 Jan 2022 19:51:29 +0200 Subject: [PATCH 11/12] Removed obsolete check for GenericMeta --- trio/_util.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/trio/_util.py b/trio/_util.py index cf33ebbedd..b5a2ceabdc 100644 --- a/trio/_util.py +++ b/trio/_util.py @@ -269,20 +269,7 @@ def __getitem__(self, _): return self -# If a new class inherits from any ABC, then the new class's metaclass has to -# inherit from ABCMeta. If a new class inherits from typing.Generic, then the -# new class's metaclass has to inherit from typing.GenericMeta. Some of the -# classes that want to use Final or NoPublicConstructor inherit from ABCs and -# generics, so Final has to inherit from these metaclasses. Fortunately, -# GenericMeta inherits from ABCMeta, so inheriting from GenericMeta alone is -# sufficient (when it exists at all). -if not t.TYPE_CHECKING and hasattr(t, "GenericMeta"): - BaseMeta = t.GenericMeta -else: - BaseMeta = ABCMeta - - -class Final(BaseMeta): +class Final(ABCMeta): """Metaclass that enforces a class to be final (i.e., subclass not allowed). If a class uses this metaclass like this:: From d2ceef8cf5ce83b3f9e5d7bc682ea6aa8c1d3a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 24 Jan 2022 20:42:40 +0200 Subject: [PATCH 12/12] Removed check for ssl.OP_NO_TLSv1_3 This flag was introduced in Python 3.7 so it's always there now. --- trio/tests/test_ssl.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/trio/tests/test_ssl.py b/trio/tests/test_ssl.py index 907d04b809..10e6c13d8f 100644 --- a/trio/tests/test_ssl.py +++ b/trio/tests/test_ssl.py @@ -60,23 +60,14 @@ TRIO_TEST_1_CERT.configure_cert(SERVER_CTX) + # TLS 1.3 has a lot of changes from previous versions. So we want to run tests # with both TLS 1.3, and TLS 1.2. -if hasattr(ssl, "OP_NO_TLSv1_3"): - # "tls13" means that we're willing to negotiate TLS 1.3. Usually that's - # what will happen, but the renegotiation tests explicitly force a - # downgrade on the server side. "tls12" means we refuse to negotiate TLS - # 1.3, so we'll almost certainly use TLS 1.2. - client_ctx_params = ["tls13", "tls12"] -else: - # We can't control whether we use TLS 1.3, so we just have to accept - # whatever openssl wants to use. This might be TLS 1.2 (if openssl is - # old), or it might be TLS 1.3 (if openssl is new, but our python version - # is too old to expose the configuration knobs). - client_ctx_params = ["default"] - - -@pytest.fixture(scope="module", params=client_ctx_params) +# "tls13" means that we're willing to negotiate TLS 1.3. Usually that's +# what will happen, but the renegotiation tests explicitly force a +# downgrade on the server side. "tls12" means we refuse to negotiate TLS +# 1.3, so we'll almost certainly use TLS 1.2. +@pytest.fixture(scope="module", params=["tls13", "tls12"]) def client_ctx(request): ctx = ssl.create_default_context()