python / python/cpython

Data race creating Tcl interpreters concurrently via `_tkinter.create` under free-threading

Aperta
#154,923 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

extension-modules topic-tkinter type-bug
Lingua principale
Python
Stelle
77.2k
Fork
35.9k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

Bug report

Bug description:

_tkinter declares Py_MOD_GIL_NOT_USED, so under --disable-gil the GIL no longer serializes calls into it. _tkinter.create() creates a new Tcl interpreter with Tcl_CreateInterp():

https://github.com/python/cpython/blob/22a6c51c94a4fde986b8964f1d36d5ec3ac20dcc/Modules/_tkinter.c#L631-L643

reached from _tkinter_create_impl:

https://github.com/python/cpython/blob/22a6c51c94a4fde986b8964f1d36d5ec3ac20dcc/Modules/_tkinter.c#L3456-L3483

_tkinter does not serialize interpreter creation, so two threads calling _tkinter.create() run Tcl_CreateInterp() concurrently. That triggers Tcl's first-time global initialization, where Tcl_MutexLock lazily initializes a static mutex. That init is not concurrency-safe, so one thread's pthread_mutex_init (write) races with another thread's pthread_mutex_lock (atomic read) on the same Tcl global.

Reproducer:

import _tkinter
from threading import Thread

def worker():
    for _ in range(20000):
        try:
            _tkinter.create(None, '', 'Tk', False, 1, False, False, None)
        except Exception:
            pass

ts = [Thread(target=worker) for _ in range(12)]
for t in ts: t.start()
for t in ts: t.join()

TSAN Report (Tested on Linux, Tcl 8.6.14, with useTk=False):

==================
WARNING: ThreadSanitizer: data race (pid=650542)
  Atomic read of size 1 at 0x72a0000145d0 by thread T2:
    #0 pthread_mutex_lock <null> 
    #1 TclCreateExecEnv /usr/src/tcl8.6-8.6.14+dfsg-1build1/generic/tclExecute.c:936:5 
    #2 cfunction_vectorcall_FASTCALL /cpython/Objects/methodobject.c:449:24 
    #3 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 
    #4 PyObject_Vectorcall /cpython/Objects/call.c:327:12 
    #5 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:768:11 
    #6 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1906:35  

  Previous write of size 1 at 0x72a0000145d0 by thread T1 (mutexes: write M0):
    #0 pthread_mutex_init <null> 
    #1 Tcl_MutexLock /usr/src/tcl8.6-8.6.14+dfsg-1build1/unix/tclUnixThrd.c:430:6 
    #2 cfunction_vectorcall_FASTCALL /cpython/Objects/methodobject.c:449:24 
    #3 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 
    #4 PyObject_Vectorcall /cpython/Objects/call.c:327:12 
    #5 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:768:11 
    #6 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1906:35  

  Location is heap block of size 16384 at 0x72a000014000 allocated by thread T1:
    #0 malloc <null> 
    #1 GetBlocks /usr/src/tcl8.6-8.6.14+dfsg-1build1/generic/tclThreadAlloc.c:1044:17 
    #2 cfunction_vectorcall_FASTCALL /cpython/Objects/methodobject.c:449:24 
    #3 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 
    #4 PyObject_Vectorcall /cpython/Objects/call.c:327:12 
    #5 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:768:11 
    #6 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1906:35 

  Mutex M0 (0x7fffb1dff7a0) created at:
    #0 pthread_mutex_lock <null>
    #1 Tcl_MutexLock /usr/src/tcl8.6-8.6.14+dfsg-1build1/unix/tclUnixThrd.c:423:2 
    #2 _PyImport_RunModInitFunc /cpython/./Python/importdl.c:436:19 
    #3 import_run_extension /cpython/Python/import.c:2167:14
    #4 _imp_create_dynamic_impl /cpython/Python/import.c:5565:11 
    #5 _imp_create_dynamic /cpython/Python/clinic/import.c.h:489:20 
    #6 cfunction_vectorcall_FASTCALL /cpython/Objects/methodobject.c:449:24 
    #7 _PyVectorcall_Call /cpython/Objects/call.c:273:16 
    #8 _PyObject_Call /cpython/Objects/call.c:348:16
    #9 PyObject_Call /cpython/Objects/call.c:373:12
    #10 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:2831:38 

SUMMARY: ThreadSanitizer: data race (/cpython/cpython-tsan/bin/python3.16t+0xfad6e)  in pthread_mutex_lock
==================

On macOS with Tcl/Tk 9.0 the same reproducer surfaces the _tkinter module-global data races (PyOS_InputHook, tcl_lock). With useTk=True it crashes inside Tk's own display initialization.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs
  • gh-156342

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia in Modules/_tkinter.c, in corrispondenza di Tcl_CreateInterp e _tkinter_create_impl, quindi riproduci le chiamate concorrenti con una build di CPython free-threaded usando ThreadSanitizer. Confronta la race di Tcl su Linux con le race a livello globale del modulo su macOS descritte nel report. Il lavoro è completato quando la creazione dell’interprete non segnala più le race identificate e non va più in crash con il riproduttore indicato; gh-156342 è già collegato come contesto.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
c, python
Ambito
desktop, operating-systems
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.