GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-115103: Implement delayed memory reclamation (QSBR) by colesbury · Pull Request #115180 · python/cpython

Conversation

@colesbury

@colesbury colesbury commented Feb 8, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

This adds a safe memory reclamation scheme for the free-threaded build based on FreeBSD's "GUS" and quiescent state based reclamation (QSBR). The API provides a mechanism for callers to detect when it is safe to free memory that may be concurrently accessed by readers.

  • Issue: Add delayed reclamation mechanism for free-threaded build (QSBR) #115103

Related PRs that build on this:

  • gh-115103: Delay reuse of mimalloc pages that store PyObjects #115435
  • gh-115103: Implement delayed free mechanism for free-threaded builds #115367

📚 Documentation preview 📚: https://cpython-previews--115180.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@colesbury
pythongh-115103: Implement delayed memory reclamation (QSBR)
ede08ee
This adds a safe memory reclamation scheme based on FreeBSD's "GUS" and
quiescent state based reclamation (QSBR). The API provides a mechanism
for callers to detect when it is safe to free memory that may be
concurrently accessed by readers.
@colesbury
colesbury requested a review from DinoV February 8, 2024 20:40
@colesbury
colesbury requested review from a team, ericsnowcurrently, gvanrossum and markshannon as code owners February 8, 2024 20:40
@bedevere-app bedevere-app Bot added the awaiting core review label Feb 8, 2024
@bedevere-app bedevere-app Bot mentioned this pull request Feb 8, 2024
Add delayed reclamation mechanism for free-threaded build (QSBR) #115103
Closed
@colesbury colesbury added skip news topic-free-threading labels Feb 8, 2024
colesbury added 2 commits February 8, 2024 20:44
@colesbury
Fix default build
a33b739
@colesbury
Mark function as static
39e3954
@colesbury

colesbury commented Feb 8, 2024

Copy link
Copy Markdown
Contributor Author

Notes for reviewers:

  • This PR does not contain any uses of the API; I wanted to keep this PR a manageable size. I'll put up a PR that makes use of these APIs soon.
  • The per-thread states (struct _qsbr_thread_state) are stored in a contiguous array to make scanning quicker, but this makes the thread initialization code a bit more complicated.
  • I think this is one of the cases where memory orderings are particularly tricky and hard to reason about intuitively. I found it helpful to verify the orderings and fences with CDSChecker. This is the model I used: https://github.com/colesbury/c11-model-checker/blob/cpython-models/test/qsbr.c.
  • _Py_qsbr_poll() will have scaling issues with large numbers of threads. That's something we'll want to deal with eventually, but possibly not in the 3.13 timeframe.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

colesbury added 2 commits February 8, 2024 21:03
@colesbury
Fix indentation in qsbr.c
f1c3740
@colesbury
Merge branch 'main' into pythongh-115103-qsbr
35cbc0e
This was referenced Feb 12, 2024
gh-115103: Implement delayed free mechanism for free-threaded builds #115367
Merged
gh-115103: Delay reuse of mimalloc pages that store PyObjects #115435
Merged
@colesbury
Merge branch 'main' into pythongh-115103-qsbr
b03c5ca
@colesbury
colesbury force-pushed the gh-115103-qsbr branch from c0034b7 to b03c5ca Compare February 14, 2024 20:32
DinoV
DinoV reviewed Feb 14, 2024
View reviewed changes
Comment thread Python/qsbr.c
if (QSBR_LT(rd_seq, min_seq)) {
// It's okay if the compare-exchange failed: another thread updated it
(void)_Py_atomic_compare_exchange_uint64(&shared->rd_seq, &rd_seq, min_seq);
rd_seq = min_seq;

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

If the compare/exchange fails is it worth returning the greater of rd_seq and min_seq?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@colesbury colesbury Feb 15, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It doesn't matter for correctness and I'm not sure it's worth any extra complexity for performance.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

DinoV reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/qsbr.c Outdated
// Starting size of the array of qsbr thread states
#define MIN_ARRAY_SIZE 8

// The shared write sequence is always odd and incremented by two. Detached

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Can we document why the shared write sequence is always odd? I'm assuming that's just to avoid issues with it wrapping around and colliding with QSBR_OFFLINE?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@colesbury colesbury Feb 15, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Yeah, that was the original motivation, but as I wrote in another comment, with 64-bit counters we don't really have to worry about wrap around.

I'm still tempted to keep this numbering scheme and the QBSR_LT wrap-around safe comparisons, but let me know what you think.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@DinoV DinoV Feb 15, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think it's fine to keep it this way, it's just worth a comment on why it's that way :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Include/internal/pycore_runtime_init.h Outdated
}, \
}, \
.qsbr = { \
.wr_seq = 1, \

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Can these be inited to QSBR_INITIAL (it seems like including pycore_qsbr.h shouldn't be an issue?)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

colesbury reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/qsbr.c
// If there are no free entries, we pause all threads, grow the array,
// and update the pointers in PyThreadState to entries in the new array.
if (qsbr == NULL) {
_PyEval_StopTheWorld(interp);

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It seems like this isn't safe to do with the mutex locked and not-detached? If another thread is trying to reserve at the same time it won't be detached, but it also won't be responding to the request for the eval breaker.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@colesbury colesbury Feb 15, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

You are right -- I mixed up the locking discipline. The outer lock should not use _Py_LOCK_DONT_DETACH. I think the locking discipline with stop-the-world should be:

  1. A lock can be acquired before starting a stop-the-world pause or after starting it, but not both (consistent lock ordering)
  2. If acquired before the stop-the-world, the acquisition should use _Py_LOCK_DETACH (the default)
  3. If acquired within the stop-the-world, the acquisition should use _Py_LOCK_DONT_DETACH

HEAD_LOCK(runtime) is an example of case 3. This is case 2.

The reasoning for case (3) is a bit subtle: locks can be directly handed off to a detached thread, so with a normal PyMutex_Lock(&m) a thread can both hold the mutex m and have paused for another thread's stop-the-world.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/qsbr.c Outdated

// Initialize (or reintialize) the freelist of QSBR thread states
static void
initialize_freelist(struct _qsbr_shared *shared)

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Maybe initialize_new_array or something like that? It's just that it's not only initing the free lists, it's also initializing all of the thread state pointers into the array (and the new part covers the re-initialization)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

colesbury reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/qsbr.c
uint64_t
_Py_qsbr_advance(struct _qsbr_shared *shared)
{
return _Py_atomic_add_uint64(&shared->wr_seq, QSBR_INCR) + QSBR_INCR;

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The FreeBSD version has logic to avoid making sure that wr_seq doesn't get too far ahead from rd_seq and if so blocks until it catches up to avoid extreme wrap around issues... Is there a reason we don't need that? :). Or should we at least add a TODO and/or an assertion?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@colesbury colesbury Feb 15, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

We are using 64-bit sequence counters to avoid worrying about wrap around. FreeBSD uses 32-bit counters. (2^62 ns = ~146 years; 2^30 ns = ~1 sec)

We could consider using 32-bit counters and handling wrap around in the future. It wouldn't matter much for x86-64 and aarch64, but would probably be more efficient on some other platforms.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@DinoV DinoV Feb 15, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Okay, that makes sense, FreeBSD's is buried in a typedef so I hadn't noticed it was 32-bit.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/qsbr.c
return true;
}

rd_seq = qsbr_poll_scan(qsbr->shared);

@DinoV DinoV Feb 14, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This is probably just related to future scaling, but I wonder if we could pass in the goal and bail on the full-scan if any thread hasn't reached it yet. It might prevent us from updating the shared for someone else who would benefit from it though...

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@colesbury colesbury Feb 15, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It's not clear to me if that case will happen often enough to be worth handling. Unfortunately, we can't really measure or test for that until we disable the GIL. With the GIL, only one thread is ever attached and active.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

DinoV reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@colesbury
Fix potential deadlock and other changes from review
94a95d5
DinoV
DinoV approved these changes Feb 15, 2024
View reviewed changes

@DinoV DinoV left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

LGTM!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting core review labels Feb 15, 2024
colesbury added 2 commits February 15, 2024 17:58
@colesbury
Add comment about QSBR_LT/QSBR_LEQ
69d5669
@colesbury
Merge branch 'main' into pythongh-115103-qsbr
be441dc
@colesbury
colesbury requested a review from sethmlarson February 16, 2024 19:16
@colesbury

colesbury commented Feb 16, 2024

Copy link
Copy Markdown
Contributor Author

@sethmlarson, does this need to be included in the SBOM? This includes code derived from a FreeBSD component, but there isn't really any shared code. In other words, it's derivative enough that I think the copyright notice should be preserved, but different enough that if there were any security vulnerabilities in the FreeBSD component, they would not be relevant here.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sethmlarson

sethmlarson commented Feb 16, 2024

Copy link
Copy Markdown
Contributor

@colesbury Thanks for the ping! Great question, the SBOM for CPython actually doesn't concern itself with copyright or licensing right now, it's primary use-case is for vulnerability management and tracking. Given this I don't think we need to update the SBOM for this contribution.

colesbury reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury
colesbury merged commit 5903190 into python:main Feb 16, 2024
@bedevere-app bedevere-app Bot removed the awaiting merge label Feb 16, 2024
@colesbury
colesbury deleted the gh-115103-qsbr branch February 16, 2024 20:25
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
@colesbury @woodruffw
pythongh-115103: Implement delayed memory reclamation (QSBR) (python#…
70236c0
…115180)

This adds a safe memory reclamation scheme based on FreeBSD's "GUS" and
quiescent state based reclamation (QSBR). The API provides a mechanism
for callers to detect when it is safe to free memory that may be
concurrently accessed by readers.
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
@colesbury @diegorusso
pythongh-115103: Implement delayed memory reclamation (QSBR) (python#…
1c8ae9d
…115180)

This adds a safe memory reclamation scheme based on FreeBSD's "GUS" and
quiescent state based reclamation (QSBR). The API provides a mechanism
for callers to detect when it is safe to free memory that may be
concurrently accessed by readers.
LukasWoodtli pushed a commit to LukasWoodtli/cpython that referenced this pull request Jan 22, 2025
@colesbury @LukasWoodtli
pythongh-115103: Implement delayed memory reclamation (QSBR) (python#…
2a9ba46
…115180)

This adds a safe memory reclamation scheme based on FreeBSD's "GUS" and
quiescent state based reclamation (QSBR). The API provides a mechanism
for callers to detect when it is safe to free memory that may be
concurrently accessed by readers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@DinoV DinoV DinoV approved these changes
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner
@gvanrossum gvanrossum Awaiting requested review from gvanrossum
@sethmlarson sethmlarson Awaiting requested review from sethmlarson

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

skip news topic-free-threading

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@colesbury @sethmlarson @DinoV
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.