GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-105766: Add Locking Around Custom Allocators by ericsnowcurrently · Pull Request #105619 · python/cpython Conversation
|
|
@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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
The rules was (is?) that you need to hold the GIL to call 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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
If the allocators are process-wide, there would need to be a lock on every call to |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Maybe we just ask custom allocators to make sure they are thread-safe? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return status; | ||
| } | ||
| HEAD_LOCK(runtime); | ||
| runtime->allocators.num_gils++; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Check for overflow. if the value is already INT_MAX pre-increment, we need to bail, even if that means SystemError.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| _PyRuntimeState *runtime = interp->runtime; | ||
| HEAD_LOCK(runtime); | ||
| runtime->allocators.num_gils--; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Add an assert(runtime->allocators.num_gils > 0); before this.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| debug_alloc_api_t obj; | ||
| } debug; | ||
| PyObjectArenaAllocator obj_arena; | ||
| int num_gils; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I suggest unsigned int
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| _PyMem_MallocLocked(void *ctx, size_t size) | ||
| { | ||
| PyMemAllocatorEx *wrapped = (PyMemAllocatorEx *)ctx; | ||
| if (_PyRuntime.allocators.num_gils > 1) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
That's a great idea. I'll try it out.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
When you're done making the requested changes, leave the comment: |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
note the discussion Eric started: https://discuss.python.org/t/what-to-do-about-custom-allocators-and-the-gil/30327 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I'm closing this. Instead, we'll specify that allocators must be thread-safe (at least when isolated subinterpreters are in play). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.