python / python/cpython

Free threading: Data race on a socket's timeout

Open
#153,935 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

3.14 3.15 3.16 extension-modules topic-free-threading type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

Bug description:

Created from gh-153852 TSAN-0035

sock->sock_timeout is a plain PyTime_t and have no synchronization. On the free-threaded build, using the same socket from multiple threads causes a c-level data race , reported by TSAN.

Repro:

import socket
import threading

s = socket.socket()
barrier = threading.Barrier(2)
N = 1000


def reader():
    barrier.wait()
    for _ in range(N):
        s.gettimeout()


def writer():
    barrier.wait()
    for _ in range(N):
        s.settimeout(1.0)


threads = [threading.Thread(target=reader), threading.Thread(target=writer)]
for t in threads:
    t.start()
for t in threads:
    t.join()
s.close()
print("done")

Output:

python.exe(20351,0x204cf7840) malloc: nano zone abandoned due to inability to reserve vm space.
==================
WARNING: ThreadSanitizer: data race (pid=20351)
  Read of size 8 at 0x00010e600198 by thread T1:
    #0 sock_gettimeout_method socketmodule.c:3320 (_socket.cpython-316t-darwin.so:arm64+0x84b8)
    #1 method_vectorcall_NOARGS descrobject.c:448 (python.exe:arm64+0x1000a0c94)
    #2 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088b88)
    #3 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:768 (python.exe:arm64+0x100290b68)
    #4 _PyEval_EvalFrameDefault generated_cases.c.h:1906 (python.exe:arm64+0x100295bc8)
    #5 _PyEval_Vector ceval.c:2172 (python.exe:arm64+0x10029000c)
    #6 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000891e8)
    #7 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x10008a984)
    #8 method_vectorcall classobject.c:55 (python.exe:arm64+0x10008de88)
    #9 context_run context.c:731 (python.exe:arm64+0x1002dff38)
    #10 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (python.exe:arm64+0x1000a0b7c)
    #11 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088b88)
    #12 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:768 (python.exe:arm64+0x100290b68)
    #13 _PyEval_EvalFrameDefault generated_cases.c.h:1906 (python.exe:arm64+0x100295bc8)
    #14 _PyEval_Vector ceval.c:2172 (python.exe:arm64+0x10029000c)
    #15 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000891e8)
    #16 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x10008a984)
    #17 method_vectorcall classobject.c:55 (python.exe:arm64+0x10008de88)
    #18 _PyObject_Call call.c:348 (python.exe:arm64+0x100088e48)
    #19 PyObject_Call call.c:373 (python.exe:arm64+0x100088ec0)
    #20 thread_run _threadmodule.c:388 (python.exe:arm64+0x1004582e0)
    #21 pythread_wrapper thread_pthread.h:234 (python.exe:arm64+0x10038fe08)

  Previous write of size 8 at 0x00010e600198 by thread T2:
    #0 sock_settimeout socketmodule.c:3264 (_socket.cpython-316t-darwin.so:arm64+0x83fc)
    #1 method_vectorcall_O descrobject.c:476 (python.exe:arm64+0x1000a0e38)
    #2 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088b88)
    #3 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:768 (python.exe:arm64+0x100290b68)
    #4 _PyEval_EvalFrameDefault generated_cases.c.h:1906 (python.exe:arm64+0x100295bc8)
    #5 _PyEval_Vector ceval.c:2172 (python.exe:arm64+0x10029000c)
    #6 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000891e8)
    #7 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x10008a984)
    #8 method_vectorcall classobject.c:55 (python.exe:arm64+0x10008de88)
    #9 context_run context.c:731 (python.exe:arm64+0x1002dff38)
    #10 method_vectorcall_FASTCALL_KEYWORDS descrobject.c:421 (python.exe:arm64+0x1000a0b7c)
    #11 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088b88)
    #12 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:768 (python.exe:arm64+0x100290b68)
    #13 _PyEval_EvalFrameDefault generated_cases.c.h:1906 (python.exe:arm64+0x100295bc8)
    #14 _PyEval_Vector ceval.c:2172 (python.exe:arm64+0x10029000c)
    #15 _PyFunction_Vectorcall call.c (python.exe:arm64+0x1000891e8)
    #16 _PyObject_VectorcallPrepend call.c:855 (python.exe:arm64+0x10008a984)
    #17 method_vectorcall classobject.c:55 (python.exe:arm64+0x10008de88)
    #18 _PyObject_Call call.c:348 (python.exe:arm64+0x100088e48)
    #19 PyObject_Call call.c:373 (python.exe:arm64+0x100088ec0)
    #20 thread_run _threadmodule.c:388 (python.exe:arm64+0x1004582e0)
    #21 pythread_wrapper thread_pthread.h:234 (python.exe:arm64+0x10038fe08)

  Thread T1 (tid=28859963, running) created by main thread at:
    #0 pthread_create <null>:126882288 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x309d8)
    #1 do_start_joinable_thread thread_pthread.h:281 (python.exe:arm64+0x10038f000)
    #2 PyThread_start_joinable_thread thread_pthread.h:323 (python.exe:arm64+0x10038ee38)
    #3 ThreadHandle_start _threadmodule.c:475 (python.exe:arm64+0x10045809c)
    #4 do_start_new_thread _threadmodule.c:1919 (python.exe:arm64+0x100457b7c)
    #5 thread_PyThread_start_joinable_thread _threadmodule.c:2042 (python.exe:arm64+0x100456ae0)
    #6 cfunction_call methodobject.c:564 (python.exe:arm64+0x1001310f4)
    #7 _PyObject_MakeTpCall call.c:242 (python.exe:arm64+0x100087f70)
    #8 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088c28)
    #9 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:768 (python.exe:arm64+0x100290b68)
    #10 _PyEval_EvalFrameDefault generated_cases.c.h:3474 (python.exe:arm64+0x100298e58)
    #11 PyEval_EvalCode ceval.c:679 (python.exe:arm64+0x10028fb74)
    #12 run_eval_code_obj pythonrun.c:1406 (python.exe:arm64+0x10036aea4)
    #13 run_mod pythonrun.c:1509 (python.exe:arm64+0x10036abd4)
    #14 _PyRun_SimpleFile pythonrun.c:544 (python.exe:arm64+0x100365d88)
    #15 _PyRun_AnyFile pythonrun.c:92 (python.exe:arm64+0x100365534)
    #16 Py_RunMain main.c:891 (python.exe:arm64+0x1003a7aec)
    #17 pymain_main main.c:921 (python.exe:arm64+0x1003a7f3c)
    #18 Py_BytesMain main.c:945 (python.exe:arm64+0x1003a8010)
    #19 main python.c:15 (python.exe:arm64+0x1000032ac)

  Thread T2 (tid=28859964, running) created by main thread at:
    #0 pthread_create <null>:126884528 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x309d8)
    #1 do_start_joinable_thread thread_pthread.h:281 (python.exe:arm64+0x10038f000)
    #2 PyThread_start_joinable_thread thread_pthread.h:323 (python.exe:arm64+0x10038ee38)
    #3 ThreadHandle_start _threadmodule.c:475 (python.exe:arm64+0x10045809c)
    #4 do_start_new_thread _threadmodule.c:1919 (python.exe:arm64+0x100457b7c)
    #5 thread_PyThread_start_joinable_thread _threadmodule.c:2042 (python.exe:arm64+0x100456ae0)
    #6 cfunction_call methodobject.c:564 (python.exe:arm64+0x1001310f4)
    #7 _PyObject_MakeTpCall call.c:242 (python.exe:arm64+0x100087f70)
    #8 PyObject_Vectorcall call.c:327 (python.exe:arm64+0x100088c28)
    #9 _Py_VectorCall_StackRefSteal ceval.c:726 (python.exe:arm64+0x1002903e4)
    #10 _PyEval_EvalFrameDefault generated_cases.c.h (python.exe:arm64+0x1002995c4)
    #11 PyEval_EvalCode ceval.c:679 (python.exe:arm64+0x10028fb74)
    #12 run_eval_code_obj pythonrun.c:1406 (python.exe:arm64+0x10036aea4)
    #13 run_mod pythonrun.c:1509 (python.exe:arm64+0x10036abd4)
    #14 _PyRun_SimpleFile pythonrun.c:544 (python.exe:arm64+0x100365d88)
    #15 _PyRun_AnyFile pythonrun.c:92 (python.exe:arm64+0x100365534)
    #16 Py_RunMain main.c:891 (python.exe:arm64+0x1003a7aec)
    #17 pymain_main main.c:921 (python.exe:arm64+0x1003a7f3c)
    #18 Py_BytesMain main.c:945 (python.exe:arm64+0x1003a8010)
    #19 main python.c:15 (python.exe:arm64+0x1000032ac)

SUMMARY: ThreadSanitizer: data race socketmodule.c:3320 in sock_gettimeout_method
==================
done
ThreadSanitizer: reported 1 warnings
[1]    20351 abort      ./python.exe repro_sock_timeout_race.py

Proposed fix:

Wrap s->sock_timeout with _Py_atomic_load_int64_relaxed

I have a fix ready

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs
  • gh-153940

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in socketmodule.c at sock_gettimeout_method (line 3320) and sock_settimeout (line 3264), then run the provided threaded reproducer under ThreadSanitizer. Done means the shared socket timeout no longer produces the reported data race; gh-153940 is already linked as the proposed fix.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
networking
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.