GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `call_soon_threadsafe` callbacks from executor no longer run before `await run_in_executor()` returns in 3.14.3 · Issue #144506 · python/cpython

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

New issue
Copy link
New issue
Copy link
Closed as not planned
Closed as not planned
call_soon_threadsafe callbacks from executor no longer run before await run_in_executor() returns in 3.14.3#144506
Copy link
Labels
3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@sairon
sairon
opened on Feb 5, 2026
Issue body actions

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:

import asyncio
import sys
import threading
from concurrent.futures import ThreadPoolExecutor

print(f"Python version: {sys.version}")


async def test_timing():
    loop = asyncio.get_running_loop()
    executor = ThreadPoolExecutor(max_workers=1)
    results = []


    def final_callback():
        results.append("final_callback")

    def intermediate_callback():
        results.append("intermediate_callback")
        loop.call_soon(final_callback)

    def thread_work():
        results.append("thread_work")
        loop.call_soon_threadsafe(intermediate_callback)

    await loop.run_in_executor(executor, thread_work)
    results.append("executor_done")

    await asyncio.sleep(0)
    results.append("after_sleep_0")

    executor.shutdown(wait=False)

    final_ran_before_sleep = (
        "final_callback" in results
        and results.index("final_callback") < results.index("after_sleep_0")
    )
    return final_ran_before_sleep, results


async def main():
    for i in range(20):
        success, results = await test_timing()
        print(f"  Iteration {i+1} {'PASS' if success else '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

Reacted by AnubhavB

Activity

  1. sairon
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Feb 5, 2026
  2. picnixz
    added
    topic-asyncio
    3.14bugs and security fixesbugs and security fixes
    3.15bugs and security fixesbugs and security fixes
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Feb 5, 2026
  3. github-project-automation
    added this to asyncioon Feb 5, 2026
  4. github-project-automation
    moved this to Todo in asyncioon Feb 5, 2026
  5. kumaraditya303 commented on Feb 6, 2026

    @kumaraditya303
    kumaraditya303
    on Feb 6, 2026
    Contributor
    More actions

    Previously, callbacks scheduled via call_soon_threadsafe from within an executor job were guaranteed to run before await run_in_executor() returned.

    Hmm, AFAIK this was never guaranteed, it just happened to work like that on previous versions. If you change the call_soon to call_later with small delay such as 0.001 in intermediate_callback then you would see this behaviour on older versions as well.

  6. kumaraditya303 commented on Mar 21, 2026

    @kumaraditya303
    kumaraditya303
    on Mar 21, 2026
    Contributor
    More actions

    I'll close this as this is being fixed on home-assistant side.

    Reacted by Jan Čermák
  7. kumaraditya303
    closed this as not plannedon Mar 21, 2026
  8. github-project-automation
    moved this from Todo to Done in asyncioon Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    • asyncio
      Status
      Done
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.