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 — bpo-46210: Fix deadlock in print. by notarealdeveloper · Pull Request #30310 · python/cpython Conversation
|
|
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept this contribution by verifying everyone involved has signed the PSF contributor agreement (CLA). CLA MissingOur records indicate the following people have not signed the CLA: For legal reasons we need all the people listed to sign the CLA before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue. If you have recently signed the CLA, please wait at least one business day You can check yourself to see if the CLA has been received. Thanks again for the contribution, we look forward to reviewing it! |
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.
Just some superficial things, I'm not that knowledgable about the whole threading/forking situation.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| /* error */ | ||
| ret = -1; | ||
| end: | ||
| return ret; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think there are a few more leaks here. Perhaps it would be simpler to initialize the variables to NULL, and then use Py_XDECREF in the end:/error: label.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (_PyObject_GetAttrId(stdio, &PyId_isatty) == NULL) { | ||
| PyErr_Clear(); | ||
| goto end; | ||
| } | ||
|
|
||
| isatty = _PyObject_CallMethodIdNoArgs(stdio, &PyId_isatty); | ||
| if (Py_IsFalse(isatty)) { | ||
| goto end; | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think this can be just one call to _PyObject_CallMethodIdNoArgs with a check for a NULL return value.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
It would be nice to see a test case added for these methods. If a threading/forking case can be made to pass consistently, that would be nice, but even if not, just a simple exercise of the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks for the tips! I know exactly what to learn about next. Will push some updates after I learn a bit more. I think the general "unlock the thing" idea probably makes sense, but I'm sure I tripped over myself in 17 different ways in the initial patch. Curious to hear from Victor about whether the overall goal is a sane one. Either way, more coming soon. Happy New Year. :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Ok, re: Just updated the pull request with what (I think?) is correct reference counting. All the tests pass, but I wanted to check for reference leaks like you suggested. So I ran several of the relevant tests with Supposing there's a reference leak, would it be noticed by Is there a standard way to check for leaks in situations like that, or is it better to just use a brute force strategy like I did in the comment above (i.e., calling every function twice and checking |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Hi there. :) After spending some time thinking about how to exercise the lock re-initialization function in a test, I realized that the best way to overcome my noobish-ness here is to follow the example of The "meat" of the implementation we've already discussed doesn't change (thanks again for the tips @sweeneyde). I've just moved things to different files, in order to maximize the similarity with what Here's what a grep of the codebase for the two functions looks like: And here's the The goal was to get line-by-line correspondence with what @vstinner does with the Now, the only difference arises inside the implementation, because the import lock is a global variable inside As for everything else: (1) the PEP 7 formatting should be good now, (2) @the-knights-who-say-ni seem to have given me the "CLA signed" badge just now, (3) in playing with all this, I stumbled on another small unrelated bug in the test suite and fixed it, so I'll submit that as a separate bpo & pull request. Let me know if there's anything else you'd like me to do. Thanks again for all the help. :) |
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.
Agree that this needs tests. It also adds a new Python-accessible method. What happens if Python code calls it?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Re: Helpful comments from @JelleZijlstra
100% agreed, the Python-accessible method wasn't necessary. I needed to learn a bit more before I could do this correctly. Just pushed some changes to address this. The updated approach:
The test's behavior is to cause the deadlock described in bpo-46210 if and only if the lock is not being reinitialized correctly. Just like the other C-level locks in the codebase, this is something that should be reinitialized on every fork, so you (@JelleZijlstra) were absolutely right to point out that a Python-accessible interface was an awkward fit. Further, I want to make it clear to other devs precisely what situation the bugfix is targeting, so the test contains a description of what the bug looks like without the fix, and which specific C function was added to fix it. Summary: the test can be run with |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
In addition to the above: In the course of adding the test, I found a small bug in the That won't be relevant to anyone who runs the test as is, since the PR fixes the deadlock. However, if anyone feels like running the above test after removing the bugfix to verify that the test fails, as it should (if we remove That's due to the above mentioned bug in Should I submit another bpo for this small bug, or just send a PR directly? Should be a one-line fix. Just need to change |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks, can you open another issue for the test.support fix? (I don't have time right now to look at this PR in detail, but I'll review it again.) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Will do! :) Thanks for the help. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| int | ||
| _PyIO_buffered_at_fork_reinit(PyObject *self) | ||
| { | ||
| Py_INCREF(self); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why not just do the INCREF after the if statement?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.
Ah, of course. Still getting a hang of reference counting. Thanks for the tips! (This one should have been obvious, now that I look at it again).
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject *buffer = NULL; | ||
| PyObject *result = NULL; | ||
|
|
||
| PyObject *stdio = _PySys_GetObjectId(key); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Looks like this returns a borrowed reference, so there's no need to DECREF it: the code is correct.
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.
Cool, thanks. :)
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.
nitpick: I suggest the name stream, but stdio works too ;-)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| PyObject *isatty = NULL; | ||
| PyObject *buffer = NULL; | ||
| PyObject *result = NULL; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is never used
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.
Removed. Thanks again.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject *stdio = _PySys_GetObjectId(key); | ||
|
|
||
| _Py_IDENTIFIER(isatty); | ||
| isatty = _PyObject_CallMethodIdNoArgs(stdio, &PyId_isatty); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I don't see the purpose of testing isatty().
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.
Fixed. :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject *buffer = NULL; | ||
|
|
||
| PyObject *stdio = _PySys_GetObjectId(key); | ||
|
|
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
stdio can be NULL or Py_None. You should do nothing in this case.
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.
Thanks, done.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (_PyObject_LookupAttrId(stdio, &PyId_buffer, &buffer) < 0) { | ||
| /* stdout.buffer and stderr.buffer are not part of the | ||
| * TextIOBase API and may not exist in some implementations. | ||
| * If not present, no need to reinitialize their locks. */ |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The function can raises an AttributeError, no? If yes, you must clear it: PyErr_Clear().
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.
Fixed! (The function is MUCH simpler now. Thanks for the tips.)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| int ret = 0; | ||
|
|
||
| PyObject *isatty = NULL; | ||
| PyObject *buffer = NULL; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I prefer to move variable declaration where the variable is set for the first value, rather than declarating most of them at the top.
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.
Changed to this style throughout.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject *buffer = NULL; | ||
| PyObject *result = NULL; | ||
|
|
||
| PyObject *stdio = _PySys_GetObjectId(key); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I suggest the name stream, but stdio works too ;-)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (!Py_IS_TYPE(self, &PyBufferedWriter_Type)) { | ||
| return 0; | ||
| } | ||
| Py_INCREF(self); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why do you hold a string pointer? _PyThread_at_fork_reinit() doesn't release the GIL, it only allocates memory on the heap. You can remove the INCREF/DECREF dance.
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.
stream over stdio. Changed.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| pass | ||
|
|
||
|
|
||
| class TestStdioAtForkReInit(unittest.TestCase): |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I dislike such function test, it's fragile and may break tomorrow. I would prefer to remove it.
If you want to keep it, you should:
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.
Can you clarify why you think it's fragile? It seems useful as a regression test. I do agree that it would be more stable in a separate script.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.
This is the only part I didn't change yet, b/c I wanted to check with you guys on the details.
@vstinner All the at_fork_reinit handlers in the codebase have your name on them, so I'll trust your judgment here. (Though I'm also curious for the same reasons @JelleZijlstra mentioned).
On the topic of the current test, you said "If you want to keep it, you should..."
What's the "else" condition for that sentence? I would prefer to do whatever you think makes the most sense. I don't have any strong opinions here, aside from a desire to have that one line in there that reinitializes the lock.
Test-wise, would you prefer (1) just moving the current test to a separate script? (2) removing the current test entirely? (3) a different test with certain properties that this one doesn't have?
I'm equally happy to do any of the above. :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| created child processes do not share locks with the parent. */ | ||
| PyStatus | ||
| _PySys_ReInitStdio(void) | ||
| { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
sys.stdin has also a buffer which has a lock which must be reinitialized at fork.
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.
Cool, now PyBufferedWriter_Type and PyBufferedReader_Type both get their locks reinitialized.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| int reinit_stderr = stdio_at_fork_reinit(&PyId_stderr); | ||
|
|
||
| if (reinit_stdout < 0 || reinit_stderr < 0) { | ||
| return _PyStatus_ERR("Failed to reinitialize stdout and stderr"); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
| return _PyStatus_ERR("Failed to reinitialize stdout and stderr"); | |
| return _PyStatus_ERR("Failed to reinitialize standard streams"); |
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.
standard streams it is! :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
This commit: * Properly identifies whether we need to reinit a stream's lock. * Changes 'stdio' to 'stream' in several functions for clarity. * Also reinitialize stdin's buffer->lock at fork.
There was an error while loading. Please reload this page.
|
Mind if I rebase this on main and force push a small update? I fixed the PR to be consistent with #30928 (bpo-46541). i.e., (P.S. Am I imagining things, or do recent changes in main feel very Sam Gross-y? Trying not to get too excited, but I hope that's what's going on. 🎉) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Generally better to merge main, so that the review history gets preserved. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Cool, so this pattern?
Apologies for the dumb question. I've used git socially and github personally, but I'm still fairly new to using github socially. :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Something like this:
GitHub also lets you do this in the web interface if there's a conflict (the "Resolve conflicts" button at the bottom of the thread). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks! |
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.