Commit graph

12 commits

Author SHA1 Message Date
Samuel Benzaquen
1a23830881 [Py/C++] Fixed data race in Python free threading by removing obsolete hack
We previously had a hack in Python/C++ Protobuf to account for the fact that LazyField did not properly remember a custom DescriptorPool or MessageFactory that was set in the ParseContext at parse time.

The code has since been fixed to properly handle the case where ParseContext contains a custom DescriptorPool/MessageFactory.  Removing the hack removes the data race under free threading.

PiperOrigin-RevId: 967236272
2026-08-19 08:59:26 -07:00
Joshua Haberman
feaa31c4d7 [Py/FreeThreading] Fixed remaining race conditions in Dealloc()
This change modifies all remaining `Dealloc()` functions to use `EraseIfEqual` if they were not already. This prevents the same race that was fixed for descriptors in cl/874084218.

PiperOrigin-RevId: 952273589
2026-07-22 12:43:18 -07:00
Joshua Haberman
6cbc7593bf Fixed race in GetMessageClass/RegisterMessageClass under free threading.
The race condition was resolved by adding a mutex lock to all accesses of the cache.

PiperOrigin-RevId: 912595698
2026-05-08 10:47:54 -07:00
Protobuf Team Bot
cecbbf41e4 Add mutex protection to DescriptorPool caches.
This change introduces a FreeThreadingMutex to PyDescriptorPool to guard access to the descriptor_options and descriptor_features hash maps.

PiperOrigin-RevId: 908115178
2026-04-30 05:47:00 -07:00
Joshua Haberman
28e451233d Fix data race in CMessage lazy initialization for Python freethreading.
This change introduces a `LazyUniquePtr` class to manage the lazy initialization of `composite_fields` and `child_submessages` within `CMessage`. When Python's GIL is disabled, `LazyUniquePtr` uses atomic operations to ensure thread-safe initialization of these maps, preventing data races when multiple threads concurrently access fields that trigger their creation. A new test case is added to reproduce and verify the fix for a race condition in `GetFieldValue`.

We are forced to use placement new and placement delete for the `LazyUniquePtr` members, because the `CMessage` struct is currently not properly constructed or destroyed.  This makes the code a bit awkward, but changing the construction/destruction of CMessage seemed like too much to bite off in this CL.

PiperOrigin-RevId: 891703488
2026-03-30 07:23:10 -07:00
Joshua Haberman
8c1a9a4b01 Fixed data race in Python Free Threading by removing unnecessary SetHasBitForRepeated() call.
This CL concerns the following functions in C++:

```c++
class Reflection {
  const Message& GetRepeatedMessage(const Message& message,
                                    const FieldDescriptor* field,
                                    int index) const;

  Message* MutableRepeatedMessage(Message* message,
                                  const FieldDescriptor* field,
                                  int index) const;
}
```

Suppose a Python program contains the following code:

```
def foo(msg: MyMessage):
  submsg = msg.repeated_foo[2]  # (A)
  if also_mutate:
    submsg.abc = 1              # (B)
```

For line (A), we currently call `MutableRepeatedMessage()` in C++ to
obtain the submessage pointer, because the resulting `submsg` object in
Python is a conceptually mutable object that will require a non-const
pointer if/when the program hits line (B).

The `MutableRepeatedMessage(Message* msg, ...)` function in C++
currently mutates `msg` by setting the hasbit of the repeated field. But
this seems unnecessary, as the function requires that the requested
sub-message already exists; it does not create a new message. So it
should not be necessary to touch the hasbit of the message, and a TGP
confirms that all tests pass if we remove this call.

Once the `SetHasBitForRepeated()` call is removed, the `MutableRepeatedMessage()`
function no longer actually mutates the `msg` argument. This makes it
effectively safe to call concurrently (ie. it will no longer trigger
Undefined Behavior in C++, and the TSAN errors will go away), which
is why it fixes the free threading test. But it leaves us in an odd
state where we are still passing a non-`const` pointer to the same
message to two functions concurrently, which is not allowed under a
normal thread-compatible contract.

An alternate solution would be to call `GetRepeatedMessage()` instead,
and `const_cast<>` away the const in the returned `const Message&`.
But this is also violates the contract: users should not be casting
away `const`.

The root cause of this odd situation is that
`MutableRepeatedMessage(Message* msg)` requires a non-`const` `msg` not
because it actually *mutates* `msg`, but because it is trying to
propagate the const-ness of `msg` to all of its children.  The proto API
generally guarantees that a const message prevents mutation of not just
the top-level message, but of the entire tree of messages. If someone
passes you a `const` message pointer, that is supposed to render the
entire tree of messages under it immutable through that pointer.

What we wish we could express in `MutableRepeatedMessage(Message* msg)`
is: "this function requires that `msg` is mutable, but this function
will not actually mutate it, and is therefore safe to call concurrently."
But there is no way of expressing this in C++.
PiperOrigin-RevId: 886830933
2026-03-20 09:29:13 -07:00
Joshua Haberman
3b0cfbf0a9 Fixed data race when accessing Python fields in free threaded builds.
To do this, we need to make CMessage use thread-safe maps for its sub-object caches.

PiperOrigin-RevId: 884515030
2026-03-16 10:28:49 -07:00
Joshua Haberman
0ed798a0d2 Fixed race condition in free threaded builds related to the descriptor cache.
Prior to this CL, it was possible for the following sequence to occur:

|Thread 1|Thread 2|
|--------|--------|
|`obj = NewDescriptor(desc)`||
|`InsertCache(desc, obj)`||
|`Py_DECREF(obj)` (to 0)||
|`Dealloc(obj) {`||
||`LookupCache(desc) -> obj`|
||`Py_INCREF(obj)`|
|`  DeleteFromCache(obj)`||
|`}`||
||`Py_DECREF(obj)`|
||`Dealloc(obj)`|

This could lead to double-`Dealloc()` calls on a single object.  These calls could race, leading to TSAN failures.

We should look deeper into whether `GcTraverse()` and `GcClear()` still need critical sections.

PiperOrigin-RevId: 874084218
2026-02-23 08:30:44 -08:00
Jie Luo
f10c1de25f Protobuf Python UPB Free Threading support.
Add obj_cache lock to pass current free threading tests on python upb.

PiperOrigin-RevId: 864903528
2026-02-03 09:04:20 -08:00
Jie Luo
b8bef14b3c Python Proto Free Threading tests/experimental
Add experimental and simple tests for free threading support on python fast cpp.

PiperOrigin-RevId: 835343204
2025-11-21 13:41:46 -08:00
Joshua Haberman
9ef8f5cffc Shorten our license headers into an abbreviated form that references LICENSE instead of including it in full.
PiperOrigin-RevId: 563897888
2023-09-08 18:50:27 -07:00
Jie Luo
3e966f1dc9 Add lock to pure python's field decoders
Fix data race that may increase flake rate of some tests.

PiperOrigin-RevId: 562044867
2023-09-01 14:15:10 -07:00