Skip to content

Conversation

@iaalm
Copy link
Contributor

@iaalm iaalm commented Nov 18, 2025

  • This issue caused by _chain_future which supports concurrent.futures.Future and asycio.Future. While asyncio.run_coroutine_threadsafe gives destination a concurrent.futures.Future, the dest_loop is None. Then dest_loop is source_loop is not good enough for the check. We should always the current running loop for the future operating.
  • In case of concurrent.futures.Future created by asyncio.run_coroutine_threadsafe, we need to wait for the cancellation before return. That's what I done in _cancel_future_in_loop.

@python-cla-bot
Copy link

python-cla-bot bot commented Nov 18, 2025

All commit authors signed the Contributor License Agreement.

CLA signed

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Thanks!

I tried this with the example from gh-105836. This indeed fixes it, but still requires the await asyncio.sleep(0) after the shutdown() call. (Also note that the example passes if there are two sleep(0) calls.)

Is there a way to do better?

Also, probably that example should be added as a unit test (preferably without long sleeps).

@iaalm
Copy link
Contributor Author

iaalm commented Nov 25, 2025

Hi @gvanrossum,

Added one (no very cool) test case. The new test failed on windows, checking..

Regarding the asyncio.sleep(0), the example in gh-105836 uses Thread.run() which actually run the function in main thread (maybe by typo?). In such cases, because future.cancel() is a sync function running in loop. There's no good way in my opinion to wait for the cancellation in future.cancel(). So one asyncio.sleep(0) is always needed to execute the cancellation work. I think that is also the reason all test in test_asyncio.test_tasks.RunCoroutineThreadsafeTests needs a asyncio.sleep(0) after cancel.
My code change is mainly avoiding another sleep to run the "future.cancel()" itself.

# emulates some worker thread (e.g. APScheduler)
    runner = threading.Thread(target=myapp.start_async_task, args=[thread_event])
    runner.run()

Does that make sense?

@gvanrossum gvanrossum marked this pull request as draft November 25, 2025 17:41
@gvanrossum gvanrossum added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes labels Nov 25, 2025
@github-project-automation github-project-automation bot moved this to Todo in asyncio Nov 25, 2025
@gvanrossum gvanrossum moved this from Todo to In Progress in asyncio Nov 25, 2025
Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Nearly there! Thanks for persevering.

@bedevere-app
Copy link

bedevere-app bot commented Nov 26, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@iaalm
Copy link
Contributor Author

iaalm commented Nov 27, 2025

will investigate why the case fail on windows tomorrow (need to find a windows device😝)

import random
import re
import sys
from threading import Event
Copy link
Member

Choose a reason for hiding this comment

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

I'd move this import into the test function -- otherwise it could be confused with asyncio.Event.

# self.loop.run_in_executor(None, _in_another_thread)
thread_future = asyncio.run_coroutine_threadsafe(self.add(1, 2), self.loop)
await asyncio.sleep(0)
target_started = Event()
Copy link
Member

Choose a reason for hiding this comment

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

I'd use import threading followed by target_started = threading.Event() to be super clear about what kind of event this is.

@iaalm
Copy link
Contributor Author

iaalm commented Dec 5, 2025

I am going to apply my style nits and then it should be perfect, right?

Thank you! Things have been hectic on my end. Come back to the PR now.

@gvanrossum
Copy link
Member

@iaalm Alas, the Windows tests still fail. Can you look into that? I suspect the timing is different because of IOCP (proactor_loop).

@iaalm
Copy link
Contributor Author

iaalm commented Dec 5, 2025

@iaalm Alas, the Windows tests still fail. Can you look into that? I suspect the timing is different because of IOCP (proactor_loop).

You're right. I tried it on a Windows VM. The best fix I can see is filter out BaseProactorEventLoop._loop_self_reading which seems always there in _ready while counting tasks.
PR updated.

@iaalm iaalm marked this pull request as ready for review December 5, 2025 16:23
Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Now the tests pass, I'm approving this.

I just want @kumaraditya303 to have a look as well.

@kumaraditya303
Copy link
Contributor

I have improved the test to not rely on _ready and removed the windows hack, the test passes on this branch while fails on current main:

Running Debug|x64 interpreter...
== CPython 3.15.0a1+ (heads/cancel_in_loop:787c9fc5de0, Dec 6 2025, 20:45:00) [MSC v.1944 64 bit (AMD64)]
== Windows-11-10.0.26200-SP0 little-endian
== Python build: debug
== cwd: D:\cpython\build\test_python_worker_24356æ
== CPU count: 12
== encodings: locale=cp1252 FS=utf-8
== resources: all test resources are disabled, use -u option to unskip tests

Using random seed: 1952231759
0:00:00 Run 1 test sequentially in a single process
0:00:00 [1/1] test_asyncio.test_tasks
test_run_coroutine_threadsafe_and_cancel (test.test_asyncio.test_tasks.RunCoroutineThreadsafeTests.test_run_coroutine_threadsafe_and_cancel) ... FAIL

======================================================================
FAIL: test_run_coroutine_threadsafe_and_cancel (test.test_asyncio.test_tasks.RunCoroutineThreadsafeTests.test_run_coroutine_threadsafe_and_cancel)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\cpython\Lib\test\test_asyncio\test_tasks.py", line 3704, in test_run_coroutine_threadsafe_and_cancel
    self.assertTrue(task.cancelled())
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
AssertionError: False is not true

----------------------------------------------------------------------
Ran 1 test in 0.014s

FAILED (failures=1)
Task was destroyed but it is pending!
task: <Task cancelling name='Task-2' coro=<sleep() done, defined at D:\cpython\Lib\asyncio\tasks.py:687> wait_for=<Future cancelled> cb=[_chain_future.<locals>._call_set_state() at D:\cpython\Lib\asyncio\futures.py:397]>
test test_asyncio.test_tasks failed
0:00:00 [1/1/1] test_asyncio.test_tasks failed (1 failure)

== Tests result: FAILURE ==

1 test failed:
    test_asyncio.test_tasks

Total duration: 542 ms
Total tests: run=1 (filtered) failures=1
Total test files: run=1/1 (filtered) failed=1
Result: FAILURE

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Thanks @kumaraditya303, that looks much better!

@kumaraditya303 kumaraditya303 enabled auto-merge (squash) December 6, 2025 19:09
@kumaraditya303 kumaraditya303 merged commit 14715e3 into python:main Dec 6, 2025
46 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in asyncio Dec 6, 2025
@miss-islington-app
Copy link

Thanks @iaalm for the PR, and @kumaraditya303 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 6, 2025
…lying cancelled asyncio task running (pythonGH-141696)

(cherry picked from commit 14715e3a64a674629c781d4a3dd11143ba010990)

Co-authored-by: Kaisheng Xu <iaalmsimon@gmail.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 6, 2025
…lying cancelled asyncio task running (pythonGH-141696)

(cherry picked from commit 14715e3)

Co-authored-by: Kaisheng Xu <iaalmsimon@gmail.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
@bedevere-app
Copy link

bedevere-app bot commented Dec 6, 2025

GH-142358 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.14 bugs and security fixes label Dec 6, 2025
@bedevere-app
Copy link

bedevere-app bot commented Dec 6, 2025

GH-142359 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants