-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Fix undefined memoryview format #2223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The code you're trying to fix has a far more serious bug: it doesn't keep a Python reference to the object it's providing a view for (
I ran this with the MSAN memory sanitizer.
With SWITCH 1:
NOTE:
I think the bigger issue needs to be fixed first. There isn't much gain from fixing just the issue with the format handling; maybe the bigger fix resolves it as a sideeffect. @aldanor Did you have a reason for not using |
Great, the fundamental issue is that the current implementation is not following the expected exporter behavior: I will leave this PR open at this moment |
@rwgk Apologies, been a bit busy so only got to replying now. Tbh I don't remember much about this, I think it was needed to make something else work (I was hacking mostly on making structured numpy dtypes work in pybind at the moment) so not enough attention was paid to this. All of the points above sound about right, all this static variable business in both |
related: #1501 |
This may end up needing a fancier construction such as a weak reference that waits for the memoryview to expire before it |
@kyamagu, do you plan to do further work on this PR? |
@wjakob I am not working on this issue at this moment, as the refcount issue seems to need a fix first. Should I go further and revise this PR? |
Both of these are really the same issue, as far as I can see. |
Alright, look into this |
Hi Kota, thanks a lot for taking on this fix! I already looked at your code a little bit and at first sight it looks good. I'm planning to repeat my test from May 23. It's a holiday weekend here, it may take me a few days, but I'll try asap. |
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.
Hi Kota, I did quite a bit of testing, including ASAN & MSAN in the Google environment (Python 2.7 & 3.6) and testing with Python 2.7 & 3.7 in a more standard Debian environment, including a low-tech leak check putting your tests into infinite loops and monitoring memory usage with top. Almost everything passes, except unfortunately for m.test_memoryview_frombuffer_new
the format comes out as B
instead of b
in the Google environment (standard Debian is fine). I need to debug. Could you please let me know if you have a suspicion what would be wrong?
Hi Kota, below are modified versions of your tests. That code passes in the Google environment with Python 2.7 & 3.7 & ASAN & MSAN, and under standard Debian with 2.7 & 3.6.
|
include/pybind11/pytypes.h
Outdated
|
||
inline memoryview::memoryview(const buffer_info& info) { | ||
// TODO: two-letter formats are not supported. | ||
static const char* formats[] = { |
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.
Observations, I have to defer to @wjakob judgement for what to do (if anything):
Here the compiler has to work very hard just to put together the array of char pointers:
static const char* formats[] = {"?", "b", "B", "h", "H", "i", "I", "q", "Q", "f", "d", "g"};
That's basically the same as the hard-coded string in detail/common.h
:
static constexpr const char c = "?bBhHiIqQfdg"[...];
It's only a subset of the format strings accepted by the current Python implementation:
Very unfortunately, this authoritative Python function is hidden away (static
).
My own choice would be to copy the function into pybind11 (probably with modifications), with a long comment to explain why. I'd also carefully check every released Python version to see if the list evolved over time, to track that in our copy, if necessary.
Alternatively, if we decide we only want the subset of format strings, I'd do a slight refactor of detail/common.h
to make the list available without the very involved detour through the C++ template engine.
@rwgk Oops, just working on a different part now, will take your comment above into |
Thanks Kota! Just today we started a new way to maintain pybind11, making team-based decisions. I'll consult with the other maintainers regarding my "Observations" above, to see what they think. |
I took a brief look: the array of standard formats is a bit on the verbose side — the more serious problem is that it will not work for more complex dtypes that are very important to some users. My suggestion (to be discussed/tested if that actually works) would be something like the following:
The owner reference will then both keep the capsule and the actual owner of the data region alive until deallocation. |
We can skip the case when the owner is not FYI, CPython internally defines ManagedBuffer Object ( Alternatively, changing the type of |
@rwgk Hi, the new commit adds tests for failure cases and an additional fix for PyPI tests. There is still an error for docs generation; the current version of breathe + sphinx + doxygen toolchain cannot correctly handle inlined |
Thanks Kota! I'll prioritize retesting with MSAN tomorrow. I'll also alert
the other maintainers to the doc gen issue.
…On Sun, Jul 12, 2020 at 10:07 PM Kota Yamaguchi ***@***.***> wrote:
@rwgk <https://github.com/rwgk> Hi, the new commit adds tests for failure
cases and an additional fix for PyPI tests.
There is still an error for docs generation; the current version of
breathe + sphinx + doxygen toolchain cannot correctly handle inlined
memoryview::frombuffer definition and produces warnings. It is still
possible to build docs by removing -W option from sphinx, but Travis CI
fails due to this warning. Do you know any workaround?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2223 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFUZAGIZVNRAV37CQB6NYTR3KJC3ANCNFSM4NHP6G7A>
.
|
Thanks Kota, I would try to see whether #if !defined(DOXYGEN) helps to get the CI to pass. Of course that means that the function won’t be documented. Speaking of that, we don’t have any documentation for this class. Especially given the tricky template definitions, would you be able to say a few words about typical use cases somewhere in this section? -> https://pybind11.readthedocs.io/en/stable/advanced/pycpp/index.html |
Hi Kota, I re-did full MSAN/ASAN testing and didn't find any problems :-) I made a few small suggested changes. The complete diffs as tested are here (is there a better way to share that with you)? https://drive.google.com/file/d/1M2iABgJ-9htyaNXj2iN4wLmQ_wqnSl9v/view?usp=sharing Please feel free to pick-and-choose. I experimented with moving the I also experimented allowing ndim == 0. MSAN/ASAN don't report issues with Python 2 & 3. The Python 2 behavior is a bit weird (see test), but Python 3 is completely sane. Since that's the future my vote is to allow ndim == 0. An additional thought is that a shape element could be 0, which boils down to the same situation, a pointer to a 0-size array. We're also not guarding against that, which I think is good. For test_memoryview, I undid my change turning |
Thanks, @rwgk 's suggestion makes sense,
Maybe you can directly edit in this PR? I have never used this functionality before, though. @wjakob Alright, I'll check the doc |
Ah, that's interesting, thanks, I'll keep that in mind for next time. I think we're nearly done polishing this PR! |
Ok, I've figured out a workaround to docs generation using a macro to skip lines: https://www.doxygen.nl/manual/faq.html#faq_code In addition, I've added const variants of |
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.
Very nice! The const overloads look useful to me.
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.
Perfect! I'll ask Yannick and Wenzel for their approvals.
Oops, py27 doesn't throw when nullptr given, will change the test |
This looks great, you have my blessing. One last minor request: in converting from SnakeCase to underscore_case, you didn't add underscores, can we have those? E.g.: |
@kyamagu Merged! Thanks a lot for the great work! |
Still applied patches: * py::dir * {object,handle}::dyn_cast Moreover: * apply pybind/pybind11#2223 to (attempt) to fix memoryview formats * use a global hash table of formats to keep format buffers alive. This is a bit better than the previous strdup hacky solutions, as memory leaks might not explode with the number of memoryviews (as long as they have the same format)
Still applied patches: * py::dir * {object,handle}::dyn_cast Moreover: * apply pybind/pybind11#2223 to (attempt) to fix memoryview formats * use a global hash table of formats to keep format buffers alive. This is a bit better than the previous strdup hacky solutions, as memory leaks might not explode with the number of memoryviews (as long as they have the same format)
Current
py::memoryview
constructor acceptspy::buffer_info
, and useinfo.format.c_str()
as a format to PyBuffer object. However, thischar*
pointer is used afterinfo
goes out of stack, leading to undefined behavior.This PR attempts to fix this, using static variable inside the constructor.
test_memoryview
calls the constructor two times with different format and check that the first memoryview is not affected by the second call.Note the format strings might be replaced by instantiated
py::format_string<T>
types, but initializer becomes complicated.