New GitHub issue #119809 from bluetech:<br>

<hr>

<pre>
# Documentation

Environment: Local build using `./configure --disable-gil`, gcc 14.1.1, Linux, x86_64, commit 1c04c63ced5038e8f45a2aac7dc45f0815a4ddc5.

Note: If `--disable-gil` is not yet ready for such issues, please feel free to close the issue.

---

What are the safety rules for `__slots__` in the free-threaded build? Is it a data race (i.e. undefined behavior) to read/write a slotted instance attribute from multiple threads concurrently?

[PEP 703](https://peps.python.org/pep-0703/) makes it clear that dicts and accordingly (I assume) also non-slotted instance attributes (which are backed by `__dict__`) are thread-safe. But the PEP doesn't mention `__slots__`, and after [asking previously](https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-3-12-updates/26503/14) I haven't been able to find a discussion of this in the Python forum or the issue tracker. So I'm opening this issue for documentation.

For reference, I tried the program below, with the following outcomes:

- `PYTHON_GIL=1` with and without `__slots__` - `counter == 80000000` -- as expected with GIL.
- `PYTHON_GIL=0` *without* `__slots__` - `counter == 28149715` -- as expected, the increment read+write is not atomic, but each read and write by itself is.
- `PYTHON_GIL=0` with `__slots__` - segmentation fault (see below for backtrace, though note it's not the same every time).

<details><summary>Test program</summary>
<p>

```py
import threading
import os

class Foo:
    # Comment this to test without slots.
 __slots__ = ('counter',)

    def __init__(self):
 self.counter = 0

FOO = Foo()

def work():
    foo = FOO
 for i in range(20_000_000):
        foo.counter += 1
 print(foo.counter)

threads = []
for i in range(os.cpu_count() - 1):
    thread = threading.Thread(target=work)
    thread.start()
 threads.append(thread)

work()
for thread in threads:
 thread.join()
```

</p>
</details> 

<details><summary>Backtrace</summary>
<p>

```
Thread 1 "python" received signal SIGSEGV, Segmentation fault.
0x00007ffff7d573ed in pthread_testcancel () from /usr/lib/libc.so.6
=> 0x00007ffff7d573ed <pthread_testcancel+13>: 8b 82 08 03 00 00       mov 0x308(%rdx),%eax
(gdb) thread apply all bt

Thread 4 (Thread 0x7ffff62006c0 (LWP 427039) "python"):
#0  0x00007ffff7db999b in sched_yield () from /usr/lib/libc.so.6
#1  0x0000555555811875 in _Py_yield () at Python/lock.c:46
#2  _PyMutex_LockTimed (m=0x555555af8160 <_PyRuntime+123872>, timeout=-1, flags=_PY_LOCK_DETACH) at Python/lock.c:94
#3  _PyMutex_LockSlow (m=m@entry=0x555555af8160 <_PyRuntime+123872>) at Python/lock.c:53
#4  0x00005555557a55b2 in PyMutex_Lock (m=0x555555af8160 <_PyRuntime+123872>) at ./Include/internal/pycore_lock.h:75
#5  _Py_brc_merge_refcounts (tstate=tstate@entry=0x555555bb7400) at Python/brc.c:126
#6 0x00005555557f2bd4 in _Py_HandlePending (tstate=0x555555bb7400) at Python/ceval_gil.c:1255
#7  0x00005555557acac0 in _PyEval_EvalFrameDefault (tstate=0x7fffffffc93ca4ad, frame=0x7ffff7fab188, throwflag=0) at Python/generated_cases.c.h:3614
#8  0x000055555563afcb in _PyObject_VectorcallTstate (tstate=0x555555bb7400, callable=<function at remote 0x57404ad6600>, args=0x7ffff61ffe18, nargsf=1, kwnames=0x0) at ./Include/internal/pycore_call.h:168
#9  method_vectorcall (method=<optimized out>, args=0x555555af4ba0 <_PyRuntime+110112>, nargsf=<optimized out>, kwnames=0x0) at Objects/classobject.c:70
#10 0x00005555558c5f5c in thread_run (boot_raw=0x555555bade20) at ./Modules/_threadmodule.c:337
#11 0x0000555555842657 in pythread_wrapper (arg=<optimized out>) at Python/thread_pthread.h:243
#12 0x00007ffff7d4fded in ?? () from /usr/lib/libc.so.6
#13 0x00007ffff7dd30dc in ?? () from /usr/lib/libc.so.6

Thread 3 (Thread 0x7ffff6c006c0 (LWP 427038) "python"):
#0  0x00005555556136f4 in PyNumber_InPlaceAdd (v=7260, w=1) at ./Include/object.h:334
#1  0x00005555557ab03a in _PyEval_EvalFrameDefault (tstate=0x57408018970, frame=0x7ffff7faf188, throwflag=0) at Python/generated_cases.c.h:132
#2  0x000055555563afcb in _PyObject_VectorcallTstate (tstate=0x555555bb2e80, callable=<function at remote 0x57404ad6600>, args=0x7ffff6bffe18, nargsf=1, kwnames=0x0) at ./Include/internal/pycore_call.h:168
#3  method_vectorcall (method=<optimized out>, args=0x555555af4ba0 <_PyRuntime+110112>, nargsf=<optimized out>, kwnames=0x0) at Objects/classobject.c:70
#4 0x00005555558c5f5c in thread_run (boot_raw=0x555555badc60) at ./Modules/_threadmodule.c:337
#5  0x0000555555842657 in pythread_wrapper (arg=<optimized out>) at Python/thread_pthread.h:243
#6 0x00007ffff7d4fded in ?? () from /usr/lib/libc.so.6
#7  0x00007ffff7dd30dc in ?? () from /usr/lib/libc.so.6

Thread 2 (Thread 0x7ffff76006c0 (LWP 427037) "python"):
#0  0x00007ffff7db999b in sched_yield () from /usr/lib/libc.so.6
#1  0x0000555555811875 in _Py_yield () at Python/lock.c:46
#2  _PyMutex_LockTimed (m=0x555555af8748 <_PyRuntime+125384>, timeout=-1, flags=_PY_LOCK_DETACH) at Python/lock.c:94
#3  _PyMutex_LockSlow (m=m@entry=0x555555af8748 <_PyRuntime+125384>) at Python/lock.c:53
#4  0x00005555557a55b2 in PyMutex_Lock (m=0x555555af8748 <_PyRuntime+125384>) at ./Include/internal/pycore_lock.h:75
#5  _Py_brc_merge_refcounts (tstate=tstate@entry=0x555555bae900) at Python/brc.c:126
#6 0x00005555557f2bd4 in _Py_HandlePending (tstate=0x555555bae900) at Python/ceval_gil.c:1255
#7  0x00005555557acac0 in _PyEval_EvalFrameDefault (tstate=0x7fffffffc93caa01, frame=0x7ffff7fb3188, throwflag=0) at Python/generated_cases.c.h:3614
#8  0x000055555563afcb in _PyObject_VectorcallTstate (tstate=0x555555bae900, callable=<function at remote 0x57404ad6600>, args=0x7ffff75ffe18, nargsf=1, kwnames=0x0) at ./Include/internal/pycore_call.h:168
#9  method_vectorcall (method=<optimized out>, args=0x555555af4ba0 <_PyRuntime+110112>, nargsf=<optimized out>, kwnames=0x0) at Objects/classobject.c:70
#10 0x00005555558c5f5c in thread_run (boot_raw=0x555555bad3e0) at ./Modules/_threadmodule.c:337
--Type <RET> for more, q to quit, c to continue without paging--c
#11 0x0000555555842657 in pythread_wrapper (arg=<optimized out>) at Python/thread_pthread.h:243
#12 0x00007ffff7d4fded in ?? () from /usr/lib/libc.so.6
#13 0x00007ffff7dd30dc in ?? () from /usr/lib/libc.so.6

Thread 1 (Thread 0x7ffff7cba740 (LWP 427033) "python"):
#0  0x00007ffff7d573ed in pthread_testcancel () from /usr/lib/libc.so.6
#1  0x00007ffff7d582d5 in sem_wait () from /usr/lib/libc.so.6
#2  0x000055555581a618 in _PySemaphore_PlatformWait (sema=0x7fffffffd830, timeout=-1) at Python/parking_lot.c:142
#3 _PySemaphore_Wait (sema=0x7fffffffd830, timeout=-1, detach=<optimized out>) at Python/parking_lot.c:213
#4  0x000055555581a7c7 in _PyParkingLot_Park (addr=addr@entry=0x5740467948a, expected=expected@entry=0x7fffffffd8b7, size=size@entry=1, timeout_ns=timeout_ns@entry=-1, park_arg=park_arg@entry=0x7fffffffd8c0, detach=detach@entry=1) at Python/parking_lot.c:316
#5  0x0000555555811833 in _PyMutex_LockTimed (m=0x5740467948a, timeout=-1, flags=_PY_LOCK_DETACH) at Python/lock.c:112
#6  _PyMutex_LockSlow (m=m@entry=0x5740467948a) at Python/lock.c:53
#7  0x00005555557d4a98 in _PyCriticalSection_BeginSlow (c=c@entry=0x7fffffffd990, m=0x5740467948a) at Python/critical_section.c:17
#8  0x0000555555693453 in _PyCriticalSection_Begin (c=0x7fffffffd990, m=0x5740467948a) at ./Include/internal/pycore_critical_section.h:212
#9 _Py_dict_lookup_threadsafe (mp=mp@entry=0x57404679480, key=key@entry='foo', hash=hash@entry=8887138504327153005, value_addr=value_addr@entry=0x7fffffffd9f0) at Objects/dictobject.c:1536
#10 0x0000555555694599 in _PyDict_LoadGlobal (globals=0x57404679480, builtins=0x5740401b140, key='foo') at Objects/dictobject.c:2450
#11 0x00005555557b076e in _PyEval_EvalFrameDefault (tstate=0x7fffffffd830, tstate@entry=0x555555b248c0 <_PyRuntime+305984>, frame=0x7ffff7fb7090, throwflag=-1, throwflag@entry=0) at Python/
</pre>

<hr>

<a href="https://github.com/python/cpython/issues/119809">View on GitHub</a>
<p>Labels: docs, topic-free-threading</p>
<p>Assignee: </p>