Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-112075: Use relaxed stores for places where we may race with when reading lock-free #115786

Merged
merged 2 commits into from
Feb 28, 2024

Conversation

DinoV
Copy link
Contributor

@DinoV DinoV commented Feb 21, 2024

With lock-free reads and iterations we can read from some places w/o locks. This adds relaxed atomic stores to those places.

Comment on lines 238 to 240
#define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELAXED(ep->me_key, key)
#define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELAXED(ep->me_value, value)
#define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELAXED(mp->ma_values->values[idx], value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want "release", although I'm not 100% sure. It's possible that the synchronization from the incref is sufficient even with relaxes stores

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I've failed to capture exactly what you're concerned about, but I think the synchronization around keys and values provides what we need: https://gist.github.com/DinoV/fd83e80e5eb37ffa197dadd9791cca6e because reads to ma_keys and ma_values are fully sequential, and writes to them are done with release stores.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that the initialization of the fields within key or value may not be fully visible. I think it's probably okay, but only because our mutex implementation uses stronger orderings than conventional locks.

For example, let's say value is a PyLongObject:

In pseudo-code:

PyLongObject *value = ...;
value->ob_digit = 123456;  // (1) non-atomic store that I'm concerned about
...
Py_BEGIN_CRITICAL_SECTION(dict);  // (2) lock dict
PyDictKeyEntry *entries = DK_ENTRIES(dict->ma_keys);
entries[...]->me_value = value; // (3) relaxed store
Py_END_CRITICAL_SECTION();

My concern was that (1) would be reordered past (3). I think it's okay because the mutexes used by critical sections use sequential consistency for both locking and unlocking, so (1) can't be reordered past (2). The "conventional" ordering is "acquire" for lock and "release" for unlock, which would not be sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think we'll be forced to lock on both threads because _Py_TryXGetRef is going to fail on the value that's not yet shared across threads and we'll be forced into the locking code path.

Copy link
Contributor

@colesbury colesbury Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about this a bit more, I think (3) really needs to be at least "release".

First, contrary to what I wrote above, the mutex locking isn't sufficient because a seq-cst operation on a variable is weaker than a seq-cst fence -- it doesn't ensure that the write in (1) is visible in this case.

I don't think we can rely on the _Py_TryXGetRef failing either. The object might have a non-zero shared refcount for a number of reasons.

Here's a simplified model. If you change the relevant "memory_order_release" to "memory_order_relaxed" it will fail:
https://github.com/colesbury/c11-model-checker/blob/cpython-models/test/dict_value.c

@DinoV DinoV marked this pull request as ready for review February 23, 2024 17:21
Copy link
Contributor

@colesbury colesbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@DinoV DinoV merged commit 81c7996 into python:main Feb 28, 2024
36 checks passed
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 25, 2024
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
@DinoV DinoV deleted the nogil_dict_atomicassign branch May 31, 2024 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants