Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop allowing subclassing public classes #1726

Merged
merged 2 commits into from
Sep 19, 2020

Conversation

pquentin
Copy link
Member

It was deprecated in Trio 0.15.0.

It was deprecated in Trio 0.15.0.
@codecov
Copy link

codecov bot commented Sep 14, 2020

Codecov Report

Merging #1726 into master will increase coverage by 0.02%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #1726      +/-   ##
==========================================
+ Coverage   99.61%   99.63%   +0.02%     
==========================================
  Files         115      114       -1     
  Lines       14445    14427      -18     
  Branches     1106     1104       -2     
==========================================
- Hits        14389    14375      -14     
+ Misses         41       37       -4     
  Partials       15       15              
Impacted Files Coverage Δ
trio/_util.py 100.00% <ø> (ø)
trio/tests/test_util.py 100.00% <ø> (ø)
trio/_core/_local.py 100.00% <100.00%> (ø)
trio/_core/_mock_clock.py 100.00% <100.00%> (ø)
trio/_core/_parking_lot.py 100.00% <100.00%> (ø)
trio/_core/_unbounded_queue.py 100.00% <100.00%> (ø)
trio/_highlevel_generic.py 100.00% <100.00%> (ø)
trio/_highlevel_socket.py 100.00% <100.00%> (ø)
trio/_path.py 100.00% <100.00%> (ø)
trio/_ssl.py 100.00% <100.00%> (ø)
... and 7 more

@pquentin
Copy link
Member Author

test_interactive failed in https://github.com/python-trio/trio/pull/1726/checks?check_run_id=1113450459:


2020-09-14T17:14:10.7485430Z _______________________________ test_interactive _______________________________
2020-09-14T17:14:10.7486910Z 
2020-09-14T17:14:10.7489150Z self = <trio.lowlevel.FdStream object at 0x111d253d0>, max_bytes = 1
2020-09-14T17:14:10.7491050Z 
2020-09-14T17:14:10.7496070Z     async def receive_some(self, max_bytes=None) -> bytes:
2020-09-14T17:14:10.7498500Z         with self._receive_conflict_detector:
2020-09-14T17:14:10.7500480Z             if max_bytes is None:
2020-09-14T17:14:10.7502330Z                 max_bytes = DEFAULT_RECEIVE_SIZE
2020-09-14T17:14:10.7504020Z             else:
2020-09-14T17:14:10.7505720Z                 if not isinstance(max_bytes, int):
2020-09-14T17:14:10.7508000Z                     raise TypeError("max_bytes must be integer >= 1")
2020-09-14T17:14:10.7509960Z                 if max_bytes < 1:
2020-09-14T17:14:10.7511930Z                     raise ValueError("max_bytes must be integer >= 1")
2020-09-14T17:14:10.7513720Z     
2020-09-14T17:14:10.7516240Z             await trio.lowlevel.checkpoint()
2020-09-14T17:14:10.7518510Z             while True:
2020-09-14T17:14:10.7519880Z                 try:
2020-09-14T17:14:10.7521670Z >                   data = os.read(self._fd_holder.fd, max_bytes)
2020-09-14T17:14:10.7524450Z E                   BlockingIOError: [Errno 35] Resource temporarily unavailable
2020-09-14T17:14:10.7526460Z 
2020-09-14T17:14:10.7532050Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_unix_pipes.py:168: BlockingIOError
2020-09-14T17:14:10.7534570Z 
2020-09-14T17:14:10.7537340Z During handling of the above exception, another exception occurred:
2020-09-14T17:14:10.7539280Z 
2020-09-14T17:14:10.7540640Z deadline = 198194.31477378527
2020-09-14T17:14:10.7541670Z 
2020-09-14T17:14:10.7543100Z     @contextmanager
2020-09-14T17:14:10.7544820Z     def fail_at(deadline):
2020-09-14T17:14:10.7547200Z         """Creates a cancel scope with the given deadline, and raises an error if it
2020-09-14T17:14:10.7549630Z         is actually cancelled.
2020-09-14T17:14:10.7551220Z     
2020-09-14T17:14:10.7553190Z         This function and :func:`move_on_at` are similar in that both create a
2020-09-14T17:14:10.7556890Z         cancel scope with a given absolute deadline, and if the deadline expires
2020-09-14T17:14:10.7560400Z         then both will cause :exc:`Cancelled` to be raised within the scope. The
2020-09-14T17:14:10.7563580Z         difference is that when the :exc:`Cancelled` exception reaches
2020-09-14T17:14:10.7569330Z         :func:`move_on_at`, it's caught and discarded. When it reaches
2020-09-14T17:14:10.7574550Z         :func:`fail_at`, then it's caught and :exc:`TooSlowError` is raised in its
2020-09-14T17:14:10.7577960Z         place.
2020-09-14T17:14:10.7579310Z     
2020-09-14T17:14:10.7580590Z         Raises:
2020-09-14T17:14:10.7582880Z           TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
2020-09-14T17:14:10.7585810Z             and caught by the context manager.
2020-09-14T17:14:10.7588320Z     
2020-09-14T17:14:10.7589450Z         """
2020-09-14T17:14:10.7590600Z     
2020-09-14T17:14:10.7592140Z         with move_on_at(deadline) as scope:
2020-09-14T17:14:10.7593850Z >           yield scope
2020-09-14T17:14:10.7594880Z 
2020-09-14T17:14:10.7599900Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_timeouts.py:105: 
2020-09-14T17:14:10.7602550Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7603500Z 
2020-09-14T17:14:10.7605060Z     async def test_interactive():
2020-09-14T17:14:10.7609980Z         # Test some back-and-forth with a subprocess. This one works like so:
2020-09-14T17:14:10.7612300Z         # in: 32\n
2020-09-14T17:14:10.7614240Z         # out: 0000...0000\n (32 zeroes)
2020-09-14T17:14:10.7615700Z         # err: 1111...1111\n (64 ones)
2020-09-14T17:14:10.7617030Z         # in: 10\n
2020-09-14T17:14:10.7618340Z         # out: 2222222222\n (10 twos)
2020-09-14T17:14:10.7619810Z         # err: 3333....3333\n (20 threes)
2020-09-14T17:14:10.7621240Z         # in: EOF
2020-09-14T17:14:10.7622510Z         # out: EOF
2020-09-14T17:14:10.7623810Z         # err: EOF
2020-09-14T17:14:10.7625000Z     
2020-09-14T17:14:10.7626560Z         async with await open_process(
2020-09-14T17:14:10.7628240Z             python(
2020-09-14T17:14:10.7629600Z                 "idx = 0\n"
2020-09-14T17:14:10.7630990Z                 "while True:\n"
2020-09-14T17:14:10.7632860Z                 "    line = sys.stdin.readline()\n"
2020-09-14T17:14:10.7637550Z                 "    if line == '': break\n"
2020-09-14T17:14:10.7639380Z                 "    request = int(line.strip())\n"
2020-09-14T17:14:10.7641290Z                 "    print(str(idx * 2) * request)\n"
2020-09-14T17:14:10.7643380Z                 "    print(str(idx * 2 + 1) * request * 2, file=sys.stderr)\n"
2020-09-14T17:14:10.7645570Z                 "    idx += 1\n"
2020-09-14T17:14:10.7647230Z             ),
2020-09-14T17:14:10.7649150Z             stdin=subprocess.PIPE,
2020-09-14T17:14:10.7651350Z             stdout=subprocess.PIPE,
2020-09-14T17:14:10.7653470Z             stderr=subprocess.PIPE,
2020-09-14T17:14:10.7655210Z         ) as proc:
2020-09-14T17:14:10.7656390Z     
2020-09-14T17:14:10.7657930Z             newline = b"\n" if posix else b"\r\n"
2020-09-14T17:14:10.7660020Z     
2020-09-14T17:14:10.7661170Z             async def expect(idx, request):
2020-09-14T17:14:10.7662910Z                 async with _core.open_nursery() as nursery:
2020-09-14T17:14:10.7664360Z     
2020-09-14T17:14:10.7665340Z                     async def drain_one(stream, count, digit):
2020-09-14T17:14:10.7666350Z                         while count > 0:
2020-09-14T17:14:10.7667500Z                             result = await stream.receive_some(count)
2020-09-14T17:14:10.7668660Z                             assert result == (
2020-09-14T17:14:10.7671550Z                                 "{}".format(digit).encode("utf-8") * len(result)
2020-09-14T17:14:10.7672570Z                             )
2020-09-14T17:14:10.7674490Z                             count -= len(result)
2020-09-14T17:14:10.7675400Z                         assert count == 0
2020-09-14T17:14:10.7676860Z                         assert await stream.receive_some(len(newline)) == newline
2020-09-14T17:14:10.7677970Z     
2020-09-14T17:14:10.7679650Z                     nursery.start_soon(drain_one, proc.stdout, request, idx * 2)
2020-09-14T17:14:10.7682520Z                     nursery.start_soon(drain_one, proc.stderr, request * 2, idx * 2 + 1)
2020-09-14T17:14:10.7684060Z     
2020-09-14T17:14:10.7684820Z             with fail_after(5):
2020-09-14T17:14:10.7688370Z                 await proc.stdin.send_all(b"12")
2020-09-14T17:14:10.7689590Z                 await sleep(0.1)
2020-09-14T17:14:10.7690680Z                 await proc.stdin.send_all(b"345" + newline)
2020-09-14T17:14:10.7691770Z                 await expect(0, 12345)
2020-09-14T17:14:10.7692910Z                 await proc.stdin.send_all(b"100" + newline + b"200" + newline)
2020-09-14T17:14:10.7694570Z                 await expect(1, 100)
2020-09-14T17:14:10.7695410Z                 await expect(2, 200)
2020-09-14T17:14:10.7696490Z                 await proc.stdin.send_all(b"0" + newline)
2020-09-14T17:14:10.7697780Z                 await expect(3, 0)
2020-09-14T17:14:10.7699070Z                 await proc.stdin.send_all(b"999999")
2020-09-14T17:14:10.7701250Z                 with move_on_after(0.1) as scope:
2020-09-14T17:14:10.7702510Z                     await expect(4, 0)
2020-09-14T17:14:10.7703560Z                 assert scope.cancelled_caught
2020-09-14T17:14:10.7705050Z                 await proc.stdin.send_all(newline)
2020-09-14T17:14:10.7707780Z                 await expect(4, 999999)
2020-09-14T17:14:10.7708840Z                 await proc.stdin.aclose()
2020-09-14T17:14:10.7710170Z >               assert await proc.stdout.receive_some(1) == b""
2020-09-14T17:14:10.7711050Z 
2020-09-14T17:14:10.7714310Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/tests/test_subprocess.py:210: 
2020-09-14T17:14:10.7715960Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7716490Z 
2020-09-14T17:14:10.7717680Z self = <trio.lowlevel.FdStream object at 0x111d253d0>, max_bytes = 1
2020-09-14T17:14:10.7719110Z 
2020-09-14T17:14:10.7723530Z     async def receive_some(self, max_bytes=None) -> bytes:
2020-09-14T17:14:10.7727290Z         with self._receive_conflict_detector:
2020-09-14T17:14:10.7728420Z             if max_bytes is None:
2020-09-14T17:14:10.7729560Z                 max_bytes = DEFAULT_RECEIVE_SIZE
2020-09-14T17:14:10.7730480Z             else:
2020-09-14T17:14:10.7731410Z                 if not isinstance(max_bytes, int):
2020-09-14T17:14:10.7732640Z                     raise TypeError("max_bytes must be integer >= 1")
2020-09-14T17:14:10.7733690Z                 if max_bytes < 1:
2020-09-14T17:14:10.7734850Z                     raise ValueError("max_bytes must be integer >= 1")
2020-09-14T17:14:10.7736310Z     
2020-09-14T17:14:10.7737450Z             await trio.lowlevel.checkpoint()
2020-09-14T17:14:10.7738640Z             while True:
2020-09-14T17:14:10.7739370Z                 try:
2020-09-14T17:14:10.7740350Z                     data = os.read(self._fd_holder.fd, max_bytes)
2020-09-14T17:14:10.7742880Z                 except BlockingIOError:
2020-09-14T17:14:10.7745170Z >                   await trio.lowlevel.wait_readable(self._fd_holder.fd)
2020-09-14T17:14:10.7746630Z 
2020-09-14T17:14:10.7749960Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_unix_pipes.py:170: 
2020-09-14T17:14:10.7751440Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7752240Z 
2020-09-14T17:14:10.7752860Z fd = 32
2020-09-14T17:14:10.7753300Z 
2020-09-14T17:14:10.7754090Z     async def wait_readable(fd):
2020-09-14T17:14:10.7755260Z         locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
2020-09-14T17:14:10.7756270Z         try:
2020-09-14T17:14:10.7757610Z >           return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
2020-09-14T17:14:10.7758770Z 
2020-09-14T17:14:10.7762530Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_generated_io_kqueue.py:38: 
2020-09-14T17:14:10.7765010Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7765600Z 
2020-09-14T17:14:10.7770220Z self = KqueueIOManager(_kqueue=<select.kqueue object at 0x111b46450>, _registered={}, _force_wakeup=<trio._core._wakeup_socketpair.WakeupSocketpair object at 0x111d3a1f0>, _force_wakeup_fd=18)
2020-09-14T17:14:10.7774930Z fd = 32
2020-09-14T17:14:10.7777010Z 
2020-09-14T17:14:10.7778850Z     @_public
2020-09-14T17:14:10.7780660Z     async def wait_readable(self, fd):
2020-09-14T17:14:10.7782890Z >       await self._wait_common(fd, select.KQ_FILTER_READ)
2020-09-14T17:14:10.7784550Z 
2020-09-14T17:14:10.7789930Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_io_kqueue.py:167: 
2020-09-14T17:14:10.7793460Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7794450Z 
2020-09-14T17:14:10.7798630Z self = KqueueIOManager(_kqueue=<select.kqueue object at 0x111b46450>, _registered={}, _force_wakeup=<trio._core._wakeup_socketpair.WakeupSocketpair object at 0x111d3a1f0>, _force_wakeup_fd=18)
2020-09-14T17:14:10.7805390Z fd = 32, filter = -1
2020-09-14T17:14:10.7806520Z 
2020-09-14T17:14:10.7808330Z     async def _wait_common(self, fd, filter):
2020-09-14T17:14:10.7810890Z         if not isinstance(fd, int):
2020-09-14T17:14:10.7812590Z             fd = fd.fileno()
2020-09-14T17:14:10.7815800Z         flags = select.KQ_EV_ADD | select.KQ_EV_ONESHOT
2020-09-14T17:14:10.7819120Z         event = select.kevent(fd, filter, flags)
2020-09-14T17:14:10.7821850Z         self._kqueue.control([event], 0)
2020-09-14T17:14:10.7825250Z     
2020-09-14T17:14:10.7827600Z         def abort(_):
2020-09-14T17:14:10.7830280Z             event = select.kevent(fd, filter, select.KQ_EV_DELETE)
2020-09-14T17:14:10.7832330Z             try:
2020-09-14T17:14:10.7834060Z                 self._kqueue.control([event], 0)
2020-09-14T17:14:10.7838260Z             except OSError as exc:
2020-09-14T17:14:10.7840620Z                 # kqueue tracks individual fds (*not* the underlying file
2020-09-14T17:14:10.7844210Z                 # object, see _io_epoll.py for a long discussion of why this
2020-09-14T17:14:10.7846330Z                 # distinction matters), and automatically deregisters an event
2020-09-14T17:14:10.7849590Z                 # if the fd is closed. So if kqueue.control says that it
2020-09-14T17:14:10.7853210Z                 # doesn't know about this event, then probably it's because
2020-09-14T17:14:10.7856700Z                 # the fd was closed behind our backs. (Too bad we can't ask it
2020-09-14T17:14:10.7858360Z                 # to wake us up when this happens, versus discovering it after
2020-09-14T17:14:10.7862130Z                 # the fact... oh well, you can't have everything.)
2020-09-14T17:14:10.7863220Z                 #
2020-09-14T17:14:10.7864290Z                 # FreeBSD reports this using EBADF. macOS uses ENOENT.
2020-09-14T17:14:10.7865890Z                 if exc.errno in (errno.EBADF, errno.ENOENT):  # pragma: no branch
2020-09-14T17:14:10.7867090Z                     pass
2020-09-14T17:14:10.7868680Z                 else:  # pragma: no cover
2020-09-14T17:14:10.7871350Z                     # As far as we know, this branch can't happen.
2020-09-14T17:14:10.7872590Z                     raise
2020-09-14T17:14:10.7873630Z             return _core.Abort.SUCCEEDED
2020-09-14T17:14:10.7874850Z     
2020-09-14T17:14:10.7876050Z >       await self.wait_kevent(fd, filter, abort)
2020-09-14T17:14:10.7877050Z 
2020-09-14T17:14:10.7879710Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_io_kqueue.py:163: 
2020-09-14T17:14:10.7881480Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7882360Z 
2020-09-14T17:14:10.7885830Z self = KqueueIOManager(_kqueue=<select.kqueue object at 0x111b46450>, _registered={}, _force_wakeup=<trio._core._wakeup_socketpair.WakeupSocketpair object at 0x111d3a1f0>, _force_wakeup_fd=18)
2020-09-14T17:14:10.7891210Z ident = 32, filter = -1
2020-09-14T17:14:10.7892630Z abort_func = <function KqueueIOManager._wait_common.<locals>.abort at 0x111889700>
2020-09-14T17:14:10.7893730Z 
2020-09-14T17:14:10.7894660Z     @_public
2020-09-14T17:14:10.7896080Z     async def wait_kevent(self, ident, filter, abort_func):
2020-09-14T17:14:10.7897700Z         key = (ident, filter)
2020-09-14T17:14:10.7899830Z         if key in self._registered:
2020-09-14T17:14:10.7901700Z             raise _core.BusyResourceError(
2020-09-14T17:14:10.7903380Z                 "attempt to register multiple listeners for same ident/filter pair"
2020-09-14T17:14:10.7904550Z             )
2020-09-14T17:14:10.7905530Z         self._registered[key] = _core.current_task()
2020-09-14T17:14:10.7907170Z     
2020-09-14T17:14:10.7908430Z         def abort(raise_cancel):
2020-09-14T17:14:10.7910550Z             r = abort_func(raise_cancel)
2020-09-14T17:14:10.7911950Z             if r is _core.Abort.SUCCEEDED:
2020-09-14T17:14:10.7913130Z                 del self._registered[key]
2020-09-14T17:14:10.7915680Z             return r
2020-09-14T17:14:10.7916570Z     
2020-09-14T17:14:10.7917580Z >       return await _core.wait_task_rescheduled(abort)
2020-09-14T17:14:10.7918440Z 
2020-09-14T17:14:10.7921770Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_io_kqueue.py:132: 
2020-09-14T17:14:10.7923820Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.7925320Z 
2020-09-14T17:14:10.7926740Z abort_func = <function KqueueIOManager.wait_kevent.<locals>.abort at 0x111889550>
2020-09-14T17:14:10.7927910Z 
2020-09-14T17:14:10.7928930Z     async def wait_task_rescheduled(abort_func):
2020-09-14T17:14:10.7931980Z         """Put the current task to sleep, with cancellation support.
2020-09-14T17:14:10.7933120Z     
2020-09-14T17:14:10.7936920Z         This is the lowest-level API for blocking in Trio. Every time a
2020-09-14T17:14:10.7940260Z         :class:`~trio.lowlevel.Task` blocks, it does so by calling this function
2020-09-14T17:14:10.7945220Z         (usually indirectly via some higher-level API).
2020-09-14T17:14:10.7946930Z     
2020-09-14T17:14:10.7948440Z         This is a tricky interface with no guard rails. If you can use
2020-09-14T17:14:10.7952740Z         :class:`ParkingLot` or the built-in I/O wait functions instead, then you
2020-09-14T17:14:10.7955660Z         should.
2020-09-14T17:14:10.7956850Z     
2020-09-14T17:14:10.7958800Z         Generally the way it works is that before calling this function, you make
2020-09-14T17:14:10.7962750Z         arrangements for "someone" to call :func:`reschedule` on the current task
2020-09-14T17:14:10.7964940Z         at some later point.
2020-09-14T17:14:10.7966250Z     
2020-09-14T17:14:10.7968100Z         Then you call :func:`wait_task_rescheduled`, passing in ``abort_func``, an
2020-09-14T17:14:10.7970190Z         "abort callback".
2020-09-14T17:14:10.7971450Z     
2020-09-14T17:14:10.7972600Z         (Terminology: in Trio, "aborting" is the process of attempting to
2020-09-14T17:14:10.7974220Z         interrupt a blocked task to deliver a cancellation.)
2020-09-14T17:14:10.7975280Z     
2020-09-14T17:14:10.7977090Z         There are two possibilities for what happens next:
2020-09-14T17:14:10.7979100Z     
2020-09-14T17:14:10.7980420Z         1. "Someone" calls :func:`reschedule` on the current task, and
2020-09-14T17:14:10.7982090Z            :func:`wait_task_rescheduled` returns or raises whatever value or error
2020-09-14T17:14:10.7983530Z            was passed to :func:`reschedule`.
2020-09-14T17:14:10.7984370Z     
2020-09-14T17:14:10.7988300Z         2. The call's context transitions to a cancelled state (e.g. due to a
2020-09-14T17:14:10.7990300Z            timeout expiring). When this happens, the ``abort_func`` is called. Its
2020-09-14T17:14:10.7991650Z            interface looks like::
2020-09-14T17:14:10.7992440Z     
2020-09-14T17:14:10.7993260Z                def abort_func(raise_cancel):
2020-09-14T17:14:10.7994100Z                    ...
2020-09-14T17:14:10.7995480Z                    return trio.lowlevel.Abort.SUCCEEDED  # or FAILED
2020-09-14T17:14:10.7996820Z     
2020-09-14T17:14:10.7997980Z            It should attempt to clean up any state associated with this call, and
2020-09-14T17:14:10.7999800Z            in particular, arrange that :func:`reschedule` will *not* be called
2020-09-14T17:14:10.8002660Z            later. If (and only if!) it is successful, then it should return
2020-09-14T17:14:10.8005920Z            :data:`Abort.SUCCEEDED`, in which case the task will automatically be
2020-09-14T17:14:10.8008090Z            rescheduled with an appropriate :exc:`~trio.Cancelled` error.
2020-09-14T17:14:10.8011410Z     
2020-09-14T17:14:10.8012620Z            Otherwise, it should return :data:`Abort.FAILED`. This means that the
2020-09-14T17:14:10.8015840Z            task can't be cancelled at this time, and still has to make sure that
2020-09-14T17:14:10.8017360Z            "someone" eventually calls :func:`reschedule`.
2020-09-14T17:14:10.8018350Z     
2020-09-14T17:14:10.8019530Z            At that point there are again two possibilities. You can simply ignore
2020-09-14T17:14:10.8021270Z            the cancellation altogether: wait for the operation to complete and
2020-09-14T17:14:10.8023050Z            then reschedule and continue as normal. (For example, this is what
2020-09-14T17:14:10.8025600Z            :func:`trio.to_thread.run_sync` does if cancellation is disabled.)
2020-09-14T17:14:10.8027540Z            The other possibility is that the ``abort_func`` does succeed in
2020-09-14T17:14:10.8031520Z            cancelling the operation, but for some reason isn't able to report that
2020-09-14T17:14:10.8034450Z            right away. (Example: on Windows, it's possible to request that an
2020-09-14T17:14:10.8036420Z            async ("overlapped") I/O operation be cancelled, but this request is
2020-09-14T17:14:10.8040350Z            *also* asynchronous – you don't find out until later whether the
2020-09-14T17:14:10.8043190Z            operation was actually cancelled or not.)  To report a delayed
2020-09-14T17:14:10.8044990Z            cancellation, then you should reschedule the task yourself, and call
2020-09-14T17:14:10.8047340Z            the ``raise_cancel`` callback passed to ``abort_func`` to raise a
2020-09-14T17:14:10.8050200Z            :exc:`~trio.Cancelled` (or possibly :exc:`KeyboardInterrupt`) exception
2020-09-14T17:14:10.8052160Z            into this task. Either of the approaches sketched below can work::
2020-09-14T17:14:10.8053260Z     
2020-09-14T17:14:10.8053940Z               # Option 1:
2020-09-14T17:14:10.8055100Z               # Catch the exception from raise_cancel and inject it into the task.
2020-09-14T17:14:10.8056960Z               # (This is what Trio does automatically for you if you return
2020-09-14T17:14:10.8058950Z               # Abort.SUCCEEDED.)
2020-09-14T17:14:10.8060800Z               trio.lowlevel.reschedule(task, outcome.capture(raise_cancel))
2020-09-14T17:14:10.8062180Z     
2020-09-14T17:14:10.8062850Z               # Option 2:
2020-09-14T17:14:10.8063960Z               # wait to be woken by "someone", and then decide whether to raise
2020-09-14T17:14:10.8065640Z               # the error from inside the task.
2020-09-14T17:14:10.8067290Z               outer_raise_cancel = None
2020-09-14T17:14:10.8069520Z               def abort(inner_raise_cancel):
2020-09-14T17:14:10.8070680Z                   nonlocal outer_raise_cancel
2020-09-14T17:14:10.8071820Z                   outer_raise_cancel = inner_raise_cancel
2020-09-14T17:14:10.8074230Z                   TRY_TO_CANCEL_OPERATION()
2020-09-14T17:14:10.8075820Z                   return trio.lowlevel.Abort.FAILED
2020-09-14T17:14:10.8077320Z               await wait_task_rescheduled(abort)
2020-09-14T17:14:10.8078630Z               if OPERATION_WAS_SUCCESSFULLY_CANCELLED:
2020-09-14T17:14:10.8079780Z                   # raises the error
2020-09-14T17:14:10.8080760Z                   outer_raise_cancel()
2020-09-14T17:14:10.8081610Z     
2020-09-14T17:14:10.8086300Z            In any case it's guaranteed that we only call the ``abort_func`` at most
2020-09-14T17:14:10.8089110Z            once per call to :func:`wait_task_rescheduled`.
2020-09-14T17:14:10.8090210Z     
2020-09-14T17:14:10.8092860Z         Sometimes, it's useful to be able to share some mutable sleep-related data
2020-09-14T17:14:10.8095770Z         between the sleeping task, the abort function, and the waking task. You
2020-09-14T17:14:10.8099660Z         can use the sleeping task's :data:`~Task.custom_sleep_data` attribute to
2020-09-14T17:14:10.8102650Z         store this data, and Trio won't touch it, except to make sure that it gets
2020-09-14T17:14:10.8105400Z         cleared when the task is rescheduled.
2020-09-14T17:14:10.8106320Z     
2020-09-14T17:14:10.8107270Z         .. warning::
2020-09-14T17:14:10.8107920Z     
2020-09-14T17:14:10.8109000Z            If your ``abort_func`` raises an error, or returns any value other than
2020-09-14T17:14:10.8110680Z            :data:`Abort.SUCCEEDED` or :data:`Abort.FAILED`, then Trio will crash
2020-09-14T17:14:10.8112580Z            violently. Be careful! Similarly, it is entirely possible to deadlock a
2020-09-14T17:14:10.8115730Z            Trio program by failing to reschedule a blocked task, or cause havoc by
2020-09-14T17:14:10.8118110Z            calling :func:`reschedule` too many times. Remember what we said up
2020-09-14T17:14:10.8122740Z            above about how you should use a higher-level API if at all possible?
2020-09-14T17:14:10.8124080Z     
2020-09-14T17:14:10.8124720Z         """
2020-09-14T17:14:10.8125950Z >       return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
2020-09-14T17:14:10.8127110Z 
2020-09-14T17:14:10.8129730Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_traps.py:166: 
2020-09-14T17:14:10.8131700Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.8133100Z 
2020-09-14T17:14:10.8134170Z self = Error(Cancelled())
2020-09-14T17:14:10.8134850Z 
2020-09-14T17:14:10.8135820Z     def unwrap(self):
2020-09-14T17:14:10.8136710Z         self._set_unwrapped()
2020-09-14T17:14:10.8141080Z         # Tracebacks show the 'raise' line below out of context, so let's give
2020-09-14T17:14:10.8142680Z         # this variable a name that makes sense out of context.
2020-09-14T17:14:10.8143940Z         captured_error = self.error
2020-09-14T17:14:10.8144940Z >       raise captured_error
2020-09-14T17:14:10.8145540Z 
2020-09-14T17:14:10.8148040Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/outcome/_sync.py:111: 
2020-09-14T17:14:10.8149890Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.8150440Z 
2020-09-14T17:14:10.8151360Z     def raise_cancel():
2020-09-14T17:14:10.8152300Z >       raise Cancelled._create()
2020-09-14T17:14:10.8153390Z E       trio.Cancelled: Cancelled
2020-09-14T17:14:10.8154450Z 
2020-09-14T17:14:10.8157300Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_core/_run.py:1167: Cancelled
2020-09-14T17:14:10.8158600Z 
2020-09-14T17:14:10.8159790Z During handling of the above exception, another exception occurred:
2020-09-14T17:14:10.8160800Z 
2020-09-14T17:14:10.8161620Z     async def test_interactive():
2020-09-14T17:14:10.8164130Z         # Test some back-and-forth with a subprocess. This one works like so:
2020-09-14T17:14:10.8165730Z         # in: 32\n
2020-09-14T17:14:10.8166880Z         # out: 0000...0000\n (32 zeroes)
2020-09-14T17:14:10.8168030Z         # err: 1111...1111\n (64 ones)
2020-09-14T17:14:10.8169710Z         # in: 10\n
2020-09-14T17:14:10.8170540Z         # out: 2222222222\n (10 twos)
2020-09-14T17:14:10.8171350Z         # err: 3333....3333\n (20 threes)
2020-09-14T17:14:10.8172090Z         # in: EOF
2020-09-14T17:14:10.8172770Z         # out: EOF
2020-09-14T17:14:10.8173450Z         # err: EOF
2020-09-14T17:14:10.8174080Z     
2020-09-14T17:14:10.8174870Z         async with await open_process(
2020-09-14T17:14:10.8175760Z             python(
2020-09-14T17:14:10.8176460Z                 "idx = 0\n"
2020-09-14T17:14:10.8177440Z                 "while True:\n"
2020-09-14T17:14:10.8178500Z                 "    line = sys.stdin.readline()\n"
2020-09-14T17:14:10.8182000Z                 "    if line == '': break\n"
2020-09-14T17:14:10.8184190Z                 "    request = int(line.strip())\n"
2020-09-14T17:14:10.8186060Z                 "    print(str(idx * 2) * request)\n"
2020-09-14T17:14:10.8188040Z                 "    print(str(idx * 2 + 1) * request * 2, file=sys.stderr)\n"
2020-09-14T17:14:10.8189330Z                 "    idx += 1\n"
2020-09-14T17:14:10.8190700Z             ),
2020-09-14T17:14:10.8191590Z             stdin=subprocess.PIPE,
2020-09-14T17:14:10.8192750Z             stdout=subprocess.PIPE,
2020-09-14T17:14:10.8193900Z             stderr=subprocess.PIPE,
2020-09-14T17:14:10.8194830Z         ) as proc:
2020-09-14T17:14:10.8195480Z     
2020-09-14T17:14:10.8196270Z             newline = b"\n" if posix else b"\r\n"
2020-09-14T17:14:10.8197880Z     
2020-09-14T17:14:10.8199580Z             async def expect(idx, request):
2020-09-14T17:14:10.8201510Z                 async with _core.open_nursery() as nursery:
2020-09-14T17:14:10.8202470Z     
2020-09-14T17:14:10.8203430Z                     async def drain_one(stream, count, digit):
2020-09-14T17:14:10.8204470Z                         while count > 0:
2020-09-14T17:14:10.8205590Z                             result = await stream.receive_some(count)
2020-09-14T17:14:10.8208120Z                             assert result == (
2020-09-14T17:14:10.8210800Z                                 "{}".format(digit).encode("utf-8") * len(result)
2020-09-14T17:14:10.8211870Z                             )
2020-09-14T17:14:10.8213700Z                             count -= len(result)
2020-09-14T17:14:10.8214640Z                         assert count == 0
2020-09-14T17:14:10.8215870Z                         assert await stream.receive_some(len(newline)) == newline
2020-09-14T17:14:10.8217220Z     
2020-09-14T17:14:10.8218370Z                     nursery.start_soon(drain_one, proc.stdout, request, idx * 2)
2020-09-14T17:14:10.8220280Z                     nursery.start_soon(drain_one, proc.stderr, request * 2, idx * 2 + 1)
2020-09-14T17:14:10.8221480Z     
2020-09-14T17:14:10.8222230Z             with fail_after(5):
2020-09-14T17:14:10.8223250Z                 await proc.stdin.send_all(b"12")
2020-09-14T17:14:10.8224240Z                 await sleep(0.1)
2020-09-14T17:14:10.8225300Z                 await proc.stdin.send_all(b"345" + newline)
2020-09-14T17:14:10.8226380Z                 await expect(0, 12345)
2020-09-14T17:14:10.8228840Z                 await proc.stdin.send_all(b"100" + newline + b"200" + newline)
2020-09-14T17:14:10.8230270Z                 await expect(1, 100)
2020-09-14T17:14:10.8231370Z                 await expect(2, 200)
2020-09-14T17:14:10.8232440Z                 await proc.stdin.send_all(b"0" + newline)
2020-09-14T17:14:10.8233510Z                 await expect(3, 0)
2020-09-14T17:14:10.8234520Z                 await proc.stdin.send_all(b"999999")
2020-09-14T17:14:10.8235820Z                 with move_on_after(0.1) as scope:
2020-09-14T17:14:10.8237990Z                     await expect(4, 0)
2020-09-14T17:14:10.8239160Z                 assert scope.cancelled_caught
2020-09-14T17:14:10.8240470Z                 await proc.stdin.send_all(newline)
2020-09-14T17:14:10.8241520Z                 await expect(4, 999999)
2020-09-14T17:14:10.8242540Z                 await proc.stdin.aclose()
2020-09-14T17:14:10.8243880Z >               assert await proc.stdout.receive_some(1) == b""
2020-09-14T17:14:10.8244810Z 
2020-09-14T17:14:10.8247840Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/tests/test_subprocess.py:210: 
2020-09-14T17:14:10.8249840Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.8251190Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/contextlib.py:131: in __exit__
2020-09-14T17:14:10.8252840Z     self.gen.throw(type, value, traceback)
2020-09-14T17:14:10.8255160Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2020-09-14T17:14:10.8255920Z 
2020-09-14T17:14:10.8256610Z deadline = 198194.31477378527
2020-09-14T17:14:10.8257160Z 
2020-09-14T17:14:10.8258580Z     @contextmanager
2020-09-14T17:14:10.8259630Z     def fail_at(deadline):
2020-09-14T17:14:10.8262220Z         """Creates a cancel scope with the given deadline, and raises an error if it
2020-09-14T17:14:10.8263710Z         is actually cancelled.
2020-09-14T17:14:10.8264520Z     
2020-09-14T17:14:10.8265600Z         This function and :func:`move_on_at` are similar in that both create a
2020-09-14T17:14:10.8268160Z         cancel scope with a given absolute deadline, and if the deadline expires
2020-09-14T17:14:10.8270500Z         then both will cause :exc:`Cancelled` to be raised within the scope. The
2020-09-14T17:14:10.8272210Z         difference is that when the :exc:`Cancelled` exception reaches
2020-09-14T17:14:10.8275250Z         :func:`move_on_at`, it's caught and discarded. When it reaches
2020-09-14T17:14:10.8277900Z         :func:`fail_at`, then it's caught and :exc:`TooSlowError` is raised in its
2020-09-14T17:14:10.8279110Z         place.
2020-09-14T17:14:10.8279730Z     
2020-09-14T17:14:10.8280430Z         Raises:
2020-09-14T17:14:10.8281920Z           TooSlowError: if a :exc:`Cancelled` exception is raised in this scope
2020-09-14T17:14:10.8283850Z             and caught by the context manager.
2020-09-14T17:14:10.8285080Z     
2020-09-14T17:14:10.8285720Z         """
2020-09-14T17:14:10.8286310Z     
2020-09-14T17:14:10.8287160Z         with move_on_at(deadline) as scope:
2020-09-14T17:14:10.8288090Z             yield scope
2020-09-14T17:14:10.8289260Z         if scope.cancelled_caught:
2020-09-14T17:14:10.8290390Z >           raise TooSlowError
2020-09-14T17:14:10.8291830Z E           trio.TooSlowError
2020-09-14T17:14:10.8292710Z 
2020-09-14T17:14:10.8295620Z ../../../../hostedtoolcache/Python/3.8.5/x64/lib/python3.8/site-packages/trio/_timeouts.py:107: TooSlowError

@pquentin
Copy link
Member Author

Merging this is fine, codecov fails just because I'm removing lines of tested code, so the percentage of untested code increases.

Copy link
Member

@altendky altendky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both deprecation limits have been surpassed. Latest release is v0.17 vs. deprecation in v0.15 which was released four months ago. All references to SubclassingDeprecatedIn_v0_15_0 were removed. All changes are about that... so it seems complete and clean.

@pquentin pquentin merged commit cabdd9f into python-trio:master Sep 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants