| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
I have no idea if this will fix the Windows build, but let's hope.
|
My concern is unnecessarily losing portability between the builtin module and the non-builtin one. This PR avoids a crash not by locking but forcing the main interpreter initialize the _datetime static types, which I think is almost equivalent to the following change in _datetimemodule.c: PyMODINIT_FUNC
PyInit__datetime(void)
{
+ if (PyDateTime_DateType.tp_subclasses == NULL) {
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ // main interp currently checks a module before subinterp imports
+ assert(_Py_IsMainInterpreter(interp));
+ init_static_types(interp, 0);
+ }
return PyModuleDef_Init(&datetimemodule);
} |
Sorry, something went wrong.
|
Hmm, that feels really hacky. I wouldn't call the movement of _datetime to static being totally unnecessary, because _datetime exposes a C API via a capsule. If _datetime isn't included with the build of Python, things using the capsule are very likely to crash, so it makes more sense to me to require it. Are there people actually disabling _datetime in their builds? |
Sorry, something went wrong.
The crash can be avoided even on pure Python level, and managed static extsension types are already special only for _datetime. The C API is likely to be redesigned in the future, which will be a standard way to shared extensions including _datetime.
Not sure about disabling. On Windows, _datetime is a built-in module that can be switched to a DLL. I know someone who want to use such a DLL as a plugin. |
Sorry, something went wrong.
|
Right, there are several ways to fix this that work, but I'm trying to find the fix that is the most correct. Relying on implementation details like tp_subclasses is a little more maintenance if we ever change type initialization, and it also muddies our general rules about how static types should work. I see several options:
We have to find a balance between correctness and simplicity, and I'm not yet sure which that is. |
Sorry, something went wrong.
This is at least a feature change more than a bug fix. |
Sorry, something went wrong.
|
Yeah, you're right. I'd be fine with a bandaid fix for 3.14 if you want to make a PR. The PyInit_ approach probably works best, but I don't fully understand why we need the tp_subclasses check. |
Sorry, something went wrong.
|
Still rough example, but checking tp_subclasses is an alternative to PyType_HasFeature(PyDateTime_DateType, Py_TPFLAGS_READY). I'm not sure which is better on free-threading. Both must be kept in the main interpreter in its life, which means interp_count is at least 1 in the runtime state and the subinterp shutdown cannot invoke fini_static_type() as final. During the _datetime exec phase, the main interpreter probably needs to skip init_static_types() to not increase interp_count. |
Sorry, something went wrong.
|
PyType_HasFeature is definitely the better option. tp_subclasses being NULL is an implementation detail, and free-threading uses non-atomic loads for the type flags (I think it probably uses stop-the-world when modifying). People look to CPython for inspiration on their own C extensions, we should encourage them to use public APIs where possible. |
Sorry, something went wrong.
|
A small concern is the startup time. Can you do a micro performance benchmark to measure the time cost of ./python -m 'pass' several times before and after this change? My expectation is that this will not add observable time cost. |
Sorry, something went wrong.
|
#136620 is ready for review.
I haven't come up with how to measure so far. |
Sorry, something went wrong.
| status = _PyDateTime_InitTypes(interp); | ||
| if (_PyStatus_EXCEPTION(status)) { | ||
| return status; | ||
| } |
There was a problem hiding this comment.
| status = _PyDateTime_InitTypes(interp); | |
| if (_PyStatus_EXCEPTION(status)) { | |
| return status; | |
| } | |
| if (!_Py_IsMainInterpreter(interp)) { | |
| status = _PyDateTime_InitTypes(interp); | |
| if (_PyStatus_EXCEPTION(status)) { | |
| return status; | |
| } | |
| } |
Reply from #136620 (comment)
Can you run test_concurrent_initialization() with this change? Based on the crashes that come from the change, I said this PR and my example are "almost equivalent." I'm not sure right now what this PR ensures.
Sorry, something went wrong.
There was a problem hiding this comment.
Hm, what are you trying to achieve here? This will just break the types for the main interpreter.
Sorry, something went wrong.
There was a problem hiding this comment.
This will just break the types for the main interpreter.
Note that test_concurrent_initialization() does not load the _datetime in the main inter interpreter at all.
Correction: Run the script of the test without running test_datetime.
Sorry, something went wrong.
|
After discussion on DPO, the consensus is that we are okay with making _datetime static, as long as we fix the capsule in the process: https://discuss.python.org/t/datetime-should-be-a-static-module/98857. @vstinner, would you mind doing a review? |
Sorry, something went wrong.
| @support.cpython_only | ||
| def test_concurrent_initialization_subinterpreter(self): | ||
| # Run in a subprocess to ensure we get a clean version of _datetime |
There was a problem hiding this comment.
Please put an anchor instead of the well-known explanation of assert_python_ok(). Also, move the test to ExtensionModuleTests (@support.cpython_only is redundant there). TestDateTime is the place to test the datetime class.
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry, I'm not sure what you mean by "an anchor".
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry about that. I meant the gh-issue number or the url.
Sorry, something went wrong.
There was a problem hiding this comment.
Ok, did both.
Sorry, something went wrong.
| print('a', end='') | ||
|
|
||
| with InterpreterPoolExecutor() as executor: | ||
| for _ in range(8): |
Sorry, something went wrong.
There was a problem hiding this comment.
Eh, I think this is fine. Many other systems have 8 cores, not 10.
Sorry, something went wrong.
There was a problem hiding this comment.
Okay, but it sounds like you are talking about the max_workers argument rather than the submit count here.
Sorry, something went wrong.
There was a problem hiding this comment.
max_workers just chooses the number of threads on the system when not set. We could try to submit os.cpu_count() number of futures, but we don't need to overcomplicate this; we just need something that stresses several subinterpreters trying to import _datetime concurrently.
Sorry, something went wrong.
| { | ||
| if (reloading) { | ||
| return 0; | ||
| } | ||
|
|
||
| // `&...` is not a constant expression according to a strict reading | ||
| // of C standards. Fill tp_base at run-time rather than statically. | ||
| // See https://bugs.python.org/issue40777 |
There was a problem hiding this comment.
This PR does not address the possible races in PyDateTime_*.tp_base = &PyDateTime_*Type; below, right?
Sorry, something went wrong.
There was a problem hiding this comment.
I guess not. I don't think there's an easy way to do this here, because atomically storing tp_base will continue to race with all the non-atomic reads elsewhere.
Do we even need to load it at runtime like this? We have other examples of directly storing it in the PyTypeObject structure:
cpython/Objects/methodobject.c
Line 396 in 958657b
Sorry, something went wrong.
There was a problem hiding this comment.
It is not needed now because the module is statically linked, that issue happens only with dynamic loaded modules so you can define it statically now.
Sorry, something went wrong.
There was a problem hiding this comment.
I guess a _Py_IsMainInterPreter() check will suffice in this PR?
Sorry, something went wrong.
There was a problem hiding this comment.
It is not needed now because the module is statically linked, that issue happens only with dynamic loaded modules so you can define it statically now.
Ah, TIL. That's definitely the best option here then.
I guess a _Py_IsMainInterPreter() check will suffice in this PR?
For what?
Sorry, something went wrong.
There was a problem hiding this comment.
For ensuring tp_base is set only once after Py_Initialize(). But I missed the Kumar 's comment.
Sorry, something went wrong.
There was a problem hiding this comment.
Please remove PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; as well.
Sorry, something went wrong.
|
🤖 New build scheduled with the buildbot fleet by @kumaraditya303 for commit 3520514 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F136583%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again. |
Sorry, something went wrong.
|
I'll merge this once buildbots pass, thanks for the reviews everyone! |
Sorry, something went wrong.
|
Thanks @ZeroIntensity for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14. |
Sorry, something went wrong.
…tialization (pythonGH-136583) `_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks. (cherry picked from commit a109606) Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
|
GH-136943 is a backport of this pull request to the 3.14 branch. |
Sorry, something went wrong.
…itialization (GH-136583) (GH-136943) gh-136421: Load `_datetime` static types during interpreter initialization (GH-136583) `_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks. (cherry picked from commit a109606) Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
…tialization (pythonGH-136583) `_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks.
…tialization (pythonGH-136583) `_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks.
…ter initialization (pythonGH-136583) (pythonGH-136943) pythongh-136421: Load `_datetime` static types during interpreter initialization (pythonGH-136583) `_datetime` is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of `_datetime`'s static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks. (cherry picked from commit a109606) Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
| Back | FazBrowse Home | New Git URL |
_datetime is a special module, because it's the only non-builtin C extension that contains static types. As such, it would initialize static types in the module's execution function, which can run concurrently. Since static type initialization is not thread-safe, this caused crashes. This fixes it by moving the initialization of _datetime's static types to interpreter startup (where all other static types are initialized), which is already properly protected through other locks.