FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-105766: Add Locking Around Custom Allocators by ericsnowcurrently · Pull Request #105619 · python/cpython · GitHub

/ cpython Public

gh-105766: Add Locking Around Custom Allocators - #105619

Closed
ericsnowcurrently wants to merge 8 commits into
python:mainfrom
ericsnowcurrently:lock-around-custom-allocators
Closed

gh-105766: Add Locking Around Custom Allocators#105619
ericsnowcurrently wants to merge 8 commits into
python:mainfrom
ericsnowcurrently:lock-around-custom-allocators

Conversation

ericsnowcurrently commented Jun 10, 2023
edited
Loading

Copy link
Copy Markdown
Member

The "mem" and "object" allocators are documented as dependent on the GIL. However, after per-interpreter GIL landed we weren't enforcing that. This fixes that by using wrapping all allocations/frees with a dedicated runtime-global lock, but only if necessary. Note that we actually use the main interpreter's GIL as that lock, but only for interpreter's that have their own GIL. Doing so closely matches the original behavior.

Copy link
Copy Markdown
Member Author

@vstinner, any objections? I tried to do this in a way that would not penalize the main interpreter or interpreters that share the GIL. Likewise, allocators that only wrap the current allocator should not be affected.

Copy link
Copy Markdown
Member

The rules was (is?) that you need to hold the GIL to call PyObject_Malloc, etc.
So why do we now need any locks (apart from the GIL)?

Presumably, the presence of per-interpreter GILs, means that allocators need to use per-interpreter state rather than process-wide state, but I don't see how this enforces that.

Copy link
Copy Markdown
Member Author

The allocators are process-global, so a per-interpreter GIL wouldn't guard against races between interpreters. Requiring that they be per-interpreter would mean we'd have to change the allocator API.

ericsnowcurrently marked this pull request as ready for review June 12, 2023 15:50

Copy link
Copy Markdown
Member

If the allocators are process-wide, there would need to be a lock on every call to ob_malloc, which would be disastrous for performance. So that doesn't sound right

Copy link
Copy Markdown
Member Author

Yeah, we lock around every allocation when a custom, non-wrapper allocator is used in a subinterpreter that has its own GIL. I don't see what else we can do, aside from doing nothing (allowing races).

Copy link
Copy Markdown
Member Author

Maybe we just ask custom allocators to make sure they are thread-safe?

ericsnowcurrently changed the title gh-100227: Add Locking Around Custom Allocators gh-105766: Add Locking Around Custom Allocators Jun 14, 2023

gpshead left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

overall yes I think something like this PR is needed for the current state of affairs. reusing the main runtime's GIL as the allocator lock is also what I'd assumed would be a natural first implementation.

Comment thread Python/pylifecycle.c Outdated
return status;
}
HEAD_LOCK(runtime);
runtime->allocators.num_gils++;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Check for overflow. if the value is already INT_MAX pre-increment, we need to bail, even if that means SystemError.

Comment thread Python/pylifecycle.c Outdated

_PyRuntimeState *runtime = interp->runtime;
HEAD_LOCK(runtime);
runtime->allocators.num_gils--;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Add an assert(runtime->allocators.num_gils > 0); before this.

Comment thread Include/internal/pycore_pymem.h Outdated
debug_alloc_api_t obj;
} debug;
PyObjectArenaAllocator obj_arena;
int num_gils;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I suggest unsigned int

Comment thread Objects/obmalloc.c
_PyMem_MallocLocked(void *ctx, size_t size)
{
PyMemAllocatorEx *wrapped = (PyMemAllocatorEx *)ctx;
if (_PyRuntime.allocators.num_gils > 1) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

You lock updates (writes) to this value. Is it safe to use without atomic access or a lock? (requiring an atomic read here would presumably be a performance hit?).

Rather than always checking... could the logic that does num_gils++ with the (main runtime) lock held also do:

... num_gils++;
if (num_gils == 2 && !has_locking_wrapper(...)) {
    maybe_add_locking_wrapper(...);
}

such that the wrapped pointer switch happens upon first creation of an additional gil while the main runtime gil (the only gil prior to the current code running) is still held.

I'm not sure it is worth doing the opposite during finalization. Once wrapped due to per subinterpreter GILs, just stay wrapped. At least the wrappers won't have this conditional anymore.

I suspect this thought is either overoptimization... or actually necessary to avoid locking/atomic access to num_gils.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

That's a great idea. I'll try it out.

Copy link
Copy Markdown

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

gpshead commented Jul 27, 2023

Copy link
Copy Markdown
Member

Copy link
Copy Markdown
Member Author

I'm closing this. Instead, we'll specify that allocators must be thread-safe (at least when isolated subinterpreters are in play).

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

4 participants


Back | FazBrowse Home | New Git URL