Skip to content

call_soon_threadsafe callbacks from executor no longer run before await run_in_executor() returns in 3.14.3 #144506

Description

@sairon

Bug report

Bug description:

Python 3.14.3 introduced a behavior change of how run_in_executor interacts with call_soon_threadsafe. Previously, callbacks scheduled via call_soon_threadsafe from within an executor job were guaranteed to run before await run_in_executor() returned.

This seems to be a direct cause of changes from gh-141696, as monkey-patching the asyncio.futures._chain_future reverts to the old behavior.

Reproducer:

importasyncioimportsysimportthreadingfromconcurrent.futuresimportThreadPoolExecutorprint(f"Python version: {sys.version}")
asyncdeftest_timing():
loop=asyncio.get_running_loop()
executor=ThreadPoolExecutor(max_workers=1)
results= []
deffinal_callback():
results.append("final_callback")
defintermediate_callback():
results.append("intermediate_callback")
loop.call_soon(final_callback)
defthread_work():
results.append("thread_work")
loop.call_soon_threadsafe(intermediate_callback)
awaitloop.run_in_executor(executor, thread_work)
results.append("executor_done")
awaitasyncio.sleep(0)
results.append("after_sleep_0")
executor.shutdown(wait=False)
final_ran_before_sleep= (
"final_callback"inresultsandresults.index("final_callback") <results.index("after_sleep_0")
)
returnfinal_ran_before_sleep, resultsasyncdefmain():
foriinrange(20):
success, results=awaittest_timing()
print(f" Iteration {i+1}{'PASS'ifsuccesselse'FAIL'}: {results}")
asyncio.run(main())

3.14.2:

Python version: 3.14.2 (main, Feb 5 2026, 13:08:22) [GCC 15.2.1 20260103]
Iteration 1 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
(rest is the same)

3.14.3:

Python version: 3.14.3 (main, Feb 5 2026, 12:15:05) [GCC 15.2.1 20260103]
Iteration 1 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 2 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 3 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 4 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 5 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 6 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 7 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 8 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 9 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 10 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 11 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 12 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 13 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 14 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 15 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 16 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 17 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 18 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 19 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']
Iteration 20 FAIL: ['thread_work', 'executor_done', 'intermediate_callback', 'after_sleep_0']

3.14.3, with asyncio.sleep(1e-15), all callbacks run but order changes:

Python version: 3.14.3 (main, Feb 5 2026, 12:15:05) [GCC 15.2.1 20260103]
Iteration 1 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 2 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 3 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 4 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 5 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 6 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 7 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 8 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 9 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 10 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 11 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 12 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 13 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 14 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 15 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 16 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 17 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 18 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']
Iteration 19 PASS: ['thread_work', 'intermediate_callback', 'final_callback', 'executor_done', 'after_sleep_0']
Iteration 20 PASS: ['thread_work', 'executor_done', 'intermediate_callback', 'final_callback', 'after_sleep_0']

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions