python / python/cpython

Free-threading: `signal.pthread_kill` deadlocks child threads

Đang mở
#150,873 6 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

extension-modules topic-free-threading type-bug
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

Context

The Dask threaded scheduler (e.g. dask.array or dask.dataframe without dask.distributed) blocks the main thread when the user calls compute() and internally spawns a concurrent.futures.ThreadPoolExecutor to schedule tasks. If a user hits Ctrl+C, they expect the program to terminate soon (at most waiting for the tasks that are halfway through execution to terminate).

Bug report

This sequence frequently deadlocks on Linux x64 free-threaded:

  1. main thread calls threading.Thread.start
  2. another thread calls signal.pthread_kill(main_thread, signal.SIGINT), which lands after start() has returned but before the thread's target function has started executing
  3. main thread handles the resulting KeyboardInterrupt
  4. main thread calls .join() on the child thread started at point 1.
  5. frequently, .join() deadlocks. A second SIGINT (e.g. from Ctrl+C) unblocks it.

Reproducer

import signal
import threading

USE_BARRIER = False
NTHREADS = 20

def demo():
    main_thread = threading.get_ident()
    barrier = threading.Barrier(NTHREADS) if USE_BARRIER else None
    thread_can_die = threading.Event()

    def f() -> None:
        if barrier:
            barrier.wait()
        thread_can_die.wait()

    def interrupt() -> None:
        if barrier:
            barrier.wait()
        signal.pthread_kill(main_thread, signal.SIGINT)

    threads = []
    for _ in range(NTHREADS - 1):
        t = threading.Thread(target=f)
        t.start()
        threads.append(t)
    try:
        t = threading.Thread(target=interrupt)
        t.start()
        threads.append(t)
        for t in threads:
            t.join()
    except KeyboardInterrupt:
        pass
    else:
        assert False, "Expected KeyboardInterrupt"

    thread_can_die.set()
    for t in threads:
        t.join()


if __name__ == "__main__":
    i = 0
    while True:
        print(i, flush=True)
        demo()
        i += 1

On these setups, the above program hangs within a handful of cycles:

  • Linux x64 3.14.5t
  • Linux x64 git tip #7a468a101268d2b13105f94ae027df8b502d0c87 (built with Tools/pixi-packages/freethreading)

Cannot reproduce on

  • Linux x64 3.14.5

Setting USE_BARRIER=True, which ensures that all child threads are already fully started when the SIGINT lands on the main thread, makes the issue disappear.

CPython versions tested on:

CPython main branch, 3.14

Operating systems tested on:

Linux

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách chạy reproducer được cung cấp trên Linux x64 free-threaded CPython, sau đó theo dõi tương tác giữa threading.Thread.start(), threading.Thread.join() và signal.pthread_kill(). Công việc được hoàn thành khi reproducer không còn bị deadlock nếu SIGINT đến trước khi target của một luồng con bắt đầu, đồng thời hành vi KeyboardInterrupt như mong đợi vẫn được giữ nguyên.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
operating-systems
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
45/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.