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-133885: Use locks instead of critical sections for _zstd by emmatyping · Pull Request #134289 · python/cpython

Conversation

@emmatyping

@emmatyping emmatyping commented May 19, 2025 •
edited by bedevere-app Bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Move from using critical sections to locks for the (de)compression methods. Since the methods allow other threads to run, we should use a lock rather than a critical section.

I'm not totally sure about the removal of the critical sections in the _get_(C|D)Dict function, but it should be safe to yield to other threads in an initializer as the values cannot be modified (ZstdDicts are immutable).

  • Issue: test_zstd failed on ubuntu with free-threading #133885

Sorry, something went wrong.

Uh oh!

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

All reactions
emmatyping added 2 commits May 19, 2025 17:28
@emmatyping
Use locks instead of critical sections
daa5f43
Also remove critical sections around code only run in initializer.
@emmatyping
Remove one more critical section
0d14dc2
@emmatyping emmatyping added skip news needs backport to 3.14 bugs and security fixes labels May 19, 2025
@bedevere-app bedevere-app Bot mentioned this pull request May 19, 2025
test_zstd failed on ubuntu with free-threading #133885
Closed
@bedevere-app bedevere-app Bot added the awaiting review label May 19, 2025
ZeroIntensity
ZeroIntensity reviewed May 20, 2025
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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 seems like a downgrade. Though rare, functions like PyMem_Malloc can call arbitrary Python code, so we lose our protection against deadlocks. What's the rationale here?

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

@colesbury
colesbury self-requested a review May 20, 2025 17:52
colesbury
colesbury reviewed May 20, 2025
View reviewed changes
Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

Comment thread Lib/test/test_zstd.py Resolved

Uh oh!

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

Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

@colesbury

colesbury commented May 20, 2025

Copy link
Copy Markdown
Contributor

@ZeroIntensity - PyMem_Malloc is not allowed to call back into Python.

All reactions

Sorry, something went wrong.

Uh oh!

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

@ZeroIntensity

ZeroIntensity commented May 20, 2025

Copy link
Copy Markdown
Member

Oh, that's news to me. When did that change?

All reactions

Sorry, something went wrong.

Uh oh!

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

@colesbury

colesbury commented May 20, 2025

Copy link
Copy Markdown
Contributor

@ZeroIntensity - that's always been the case, as far as I know. A lot of things would break if PyMem_Malloc were allowed to call back into Python. PyObject_GC_Malloc used to be able to call back into Python (via triggering GC), but that hasn't been the case since 3.12.

All reactions

Sorry, something went wrong.

Uh oh!

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

@ZeroIntensity

ZeroIntensity commented May 20, 2025

Copy link
Copy Markdown
Member

PyObject_GC_Malloc used to be able to call back into Python (via triggering GC), but that hasn't been the case since 3.12.

Ah, that's what I was thinking of.

We do need to document this contract somewhere. I'm imagining a case where an embedder sets their own allocator that calls Python code, because they have an attached thread state. The documentation doesn't say you can't do that, as far as I can tell.

All reactions

Sorry, something went wrong.

Uh oh!

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

emmatyping added 6 commits May 20, 2025 17:28
@emmatyping
Make capsule management threadsafe
1b68e1e
Using PyDict_SetDefaultRef allows us to not have contention on the
capsules dictionary shared by ZstdDict instances.
@emmatyping
Add a test for sharing a dictionary across threads
f6602f4
@emmatyping
Move lock check in dealloc to an assert
d8854c5
@emmatyping
Run threading checks on GIL builds too
02fc2b3
@emmatyping
Remove extra blank line
edbf444
@emmatyping
Remove extraneous whitespace
bf287cf
ZeroIntensity
ZeroIntensity reviewed May 21, 2025
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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.

Ok, I understand this a little better now, sorry!

I'm a bit iffy about the lock-ordering here. I think the lock is only held under non-reentrant calls? PyErr_SetString and friends seem to be safe, but it looks like they might be able to re-enter if someone did weird things to the exception state. I wouldn't worry about it, really, but it's worth bringing up.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Lib/test/test_zstd.py Resolved

Uh oh!

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

Comment thread Modules/_zstd/decompressor.c Resolved

Uh oh!

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

Comment thread Modules/_zstd/decompressor.c Resolved

Uh oh!

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

emmatyping added 2 commits May 21, 2025 00:10
@emmatyping
Remove unused includes
005d1a0
@emmatyping
Revert "Remove unused includes"
84c5b30
This reverts commit 005d1a0.
@dpdani

dpdani commented May 21, 2025

Copy link
Copy Markdown
Contributor

Are we sure about the implications of implicit locking in de/compressor contexts?
I believe we might carefully implement this now only to discourage it later.

If two threads concurrently write into a compressor context with implicit locking they might succeed in corrupting their data. Say:

  1. thread 1 writes <html>
  2. thread 2 writes a png file
  3. thread 1 writes </html>

Or pick whichever other interleaving of the two threads.
The corruption is only evident after decompressing back the data: from zstd's point of view there is nothing wrong with the above.

This is just one example, but I think there's reasonable expectation that users won't want to share de/compressor contexts anyways.

I think raising an exception when detecting concurrent use is a more desirable outcome.

All reactions

Sorry, something went wrong.

Uh oh!

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

@emmatyping

emmatyping commented May 21, 2025

Copy link
Copy Markdown
Member Author

If two threads concurrently write into a compressor context with implicit locking they might succeed in corrupting their data. Say:

thread 1 writes
thread 2 writes a png file
thread 1 writes

I think I would say don't write different data to the same stream with threading. I can append things to a shared list that makes it invalid for what I want but that is a programmer error. I don't particularly see a reason to share a compression context between threads, but I do think we might see people move a compression context into a thread to do all the compression in another thread, and we should try to support that.

All reactions

Sorry, something went wrong.

Uh oh!

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

emmatyping added 4 commits May 21, 2025 09:46
@emmatyping
Put locks around ZstdDict usage
ec31019
This also reverts the setdefault usage which we don't need anymore and refactors the _zstd_load_(c,d)_dict functions to not use goto/properly lock around dictionary usage.
@emmatyping
Add tests for sharing all types of ZstdDict
2dbed4e
@emmatyping
Remove @critical_section
afdabd5
@emmatyping
Add missing include
d8d48c2
@dpdani

dpdani commented May 21, 2025

Copy link
Copy Markdown
Contributor

Oh yes, I agree.

Let me explain a bit better what my intention was with python-zstandard.

Instead of using a lock-based approach, I used a flag to signify that a de/compressor context was in use by some thread.
If another thread tried to set the same de/compressor's flag (with an atomic compare-and-set operation) while it was still in use, then it would raise an exception.

The differences with using locks are:

  1. a ConcurrentUseException is raised when appropriate, which should
  2. make users be aware that data corruption may occur, caused by multithreading.

And, in both approaches:

  1. no segmentation fault can be issued by the underlying zstd implementation.

IIUC, you alluded to an ownership transfer model. Note that this approach is not in contrast with that model. If a de/compressor object is created by one thread and then transferred to a different thread for use, no exception is raised. The only problematic usage that the exception would signal is when a thread calls into a context method while another thread was already using the context.

In fact, this is not even in contrast with other thread synchronization mechanisms that don't use ownership transfer (e.g. barriers): if the program makes sure that there is no concurrent access, then the exception is never raised.

Of course, the choice is up to you. I just wanted to make sure we considered both approaches.

All reactions

Sorry, something went wrong.

Uh oh!

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

@ZeroIntensity

ZeroIntensity commented May 21, 2025

Copy link
Copy Markdown
Member

Hmm, I don't think we should be inventing object ownership models here. That makes sense for a third-party, but that sort of thing should get its own proposal for the stdlib as a whole.

Locking should be sufficient, right? If torn writes at the method-level are an issue, we should just document that users need to have an additional layer of synchronization when using compressors concurrently.

All reactions

Sorry, something went wrong.

Uh oh!

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

kumaraditya303
kumaraditya303 reviewed May 22, 2025
View reviewed changes
Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

kumaraditya303
kumaraditya303 reviewed May 22, 2025
View reviewed changes
Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

emmatyping and others added 2 commits May 22, 2025 14:27
@emmatyping @kumaraditya303
Pass NULL to PyDict_SetDefaultRef
43d3dea
Co-authored-by: Kumar Aditya <[email protected]>
@emmatyping
Put locks around relevant functions and use PyDict_GetItemRef
93dec23
ZeroIntensity
ZeroIntensity reviewed May 23, 2025
View reviewed changes
Comment thread Modules/_zstd/compressor.c Resolved

Uh oh!

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

@emmatyping
Remove unused decref
69a755b
@emmatyping

emmatyping commented May 23, 2025

Copy link
Copy Markdown
Member Author

!buildbot nogil refleak

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-bot

bedevere-bot commented May 23, 2025

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @emmatyping for commit 69a755b 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F134289%2Fmerge

The command will test the builders whose names match following regular expression: nogil refleak

The builders matched are:

  • AMD64 CentOS9 NoGIL Refleaks PR
  • PPC64LE Fedora Rawhide NoGIL refleaks PR
  • AMD64 Fedora Rawhide NoGIL refleaks PR
  • s390x Fedora Rawhide NoGIL refleaks PR
  • aarch64 Fedora Rawhide NoGIL refleaks PR
All reactions

Sorry, something went wrong.

Uh oh!

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

ZeroIntensity
ZeroIntensity approved these changes May 23, 2025
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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 assuming buildbots pass

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 core review and removed awaiting review labels May 23, 2025
@emmatyping

emmatyping commented May 23, 2025

Copy link
Copy Markdown
Member Author

Hm, that CentOS 9 buildbot doesn't have zstd installed but it is failing due to unrelated issues.

All reactions

Sorry, something went wrong.

Uh oh!

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

@emmatyping

emmatyping commented May 23, 2025

Copy link
Copy Markdown
Member Author

(FWIW, I ran the refleak hunter locally on a nogil build and it passed)

All reactions

Sorry, something went wrong.

Uh oh!

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

@emmatyping

emmatyping commented May 23, 2025

Copy link
Copy Markdown
Member Author

Opened #134557 for the free threaded buildbot refleak

All reactions

Sorry, something went wrong.

Uh oh!

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

colesbury
colesbury approved these changes May 23, 2025
View reviewed changes
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting core review labels May 23, 2025
@colesbury
colesbury merged commit 8dbc119 into python:main May 23, 2025
@miss-islington-app

miss-islington-app Bot commented May 23, 2025

Copy link
Copy Markdown

Thanks @emmatyping for the PR, and @colesbury for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14.
🐍🍒⛏🤖

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-app bedevere-app Bot removed the awaiting merge label May 23, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request May 23, 2025
@emmatyping @miss-islington
pythongh-133885: Use locks instead of critical sections for _zstd (py…
03cee3f
…thongh-134289)

Move from using critical sections to locks for the (de)compression methods.
Since the methods allow other threads to run, we should use a lock rather
than a critical section.
(cherry picked from commit 8dbc119)

Co-authored-by: Emma Smith <[email protected]>
@bedevere-app

bedevere-app Bot commented May 23, 2025

Copy link
Copy Markdown

GH-134560 is a backport of this pull request to the 3.14 branch.

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label May 23, 2025
@emmatyping

emmatyping commented May 23, 2025

Copy link
Copy Markdown
Member Author

Thanks all for the reviews! Thinking through free threading reentrancy at the C level is something I'm still getting a feel for. Chatting about this fix has definitely helped with my understanding :)

ZeroIntensity reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

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

colesbury pushed a commit that referenced this pull request May 23, 2025
@miss-islington @emmatyping
[3.14] gh-133885: Use locks instead of critical sections for _zstd (g…
8167594
…h-134289) (gh-134560)

Move from using critical sections to locks for the (de)compression methods.
Since the methods allow other threads to run, we should use a lock rather
than a critical section.
(cherry picked from commit 8dbc119)

Co-authored-by: Emma Smith <[email protected]>
@ZeroIntensity ZeroIntensity mentioned this pull request May 28, 2025
gh-134009: Expose PyMutex_IsLocked in the public C API #134365
Merged
Pranjal095 pushed a commit to Pranjal095/cpython that referenced this pull request Jul 12, 2025
@emmatyping @Pranjal095
pythongh-133885: Use locks instead of critical sections for _zstd (py…
46b0f13
…thongh-134289)

Move from using critical sections to locks for the (de)compression methods.
Since the methods allow other threads to run, we should use a lock rather
than a critical section.
taegyunkim pushed a commit to taegyunkim/cpython that referenced this pull request Aug 4, 2025
@emmatyping @taegyunkim
pythongh-133885: Use locks instead of critical sections for _zstd (py…
563547e
…thongh-134289)

Move from using critical sections to locks for the (de)compression methods.
Since the methods allow other threads to run, we should use a lock rather
than a critical section.
gostak-dd pushed a commit to gostak-dd/cpython that referenced this pull request Jun 2, 2026
@emmatyping @gostak-dd
pythongh-133885: Use locks instead of critical sections for _zstd (py…
55f0181
…thongh-134289)

Move from using critical sections to locks for the (de)compression methods.
Since the methods allow other threads to run, we should use a lock rather
than a critical section.
@devdanzin devdanzin mentioned this pull request Jul 17, 2026
Free-threading data races in CPython: 15 findings (9 new + 6 residual of existing FT work) #153852
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@kumaraditya303 kumaraditya303 kumaraditya303 left review comments
@colesbury colesbury colesbury approved these changes
@ZeroIntensity ZeroIntensity ZeroIntensity approved these changes

Uh oh!

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

Assignees

No one assigned

Labels

skip news

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.

6 participants

@emmatyping @colesbury @ZeroIntensity @dpdani @bedevere-bot @kumaraditya303
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.