-
Notifications
You must be signed in to change notification settings - Fork 1.4k
testPixaReplacePix crashes in native code #159
Comments
Still crashes (if uncommented). It doesn't crash if one of What's worse, the test completes successfully when I run it alone. |
On the face of it, the cause is that |
Not a bug. The previous pix in the pixa is destroyed, and the pixa takes ownership of the new pix, so its refcount is unchanged:
I'll add to the notes this: |
So, it's kind of move operation? To avoid the mistakes like in the Java test, the replacing pix should somehow be marked invalid. But I must say that I don't see why increasing refcount is less clean. |
Leptonica is consistent in having a container own the objects in it. So when we put the pix in the pixa, the pixa owns it. Now, we could put a copy (or clone) of the pix in, but then you'd have the old one lying around and have to remember to destroy it. Just asking for a memory leak. Not good. So by default, the pix is INSERTED into the pixa. If you look at pixaAddPix(), you'll see that you have several choices. I decided not to include those choices in this function, because it makes the interface (and its use) more complicated, and for no reason at all, because if you put the pix in a pixa, you almost certainly don't want to mess with it using the original handle. To be more safe, I could have nulled the original handle by the function (by passing in its address and dereferencing inside), to prevent an accidental double free, but again, it seemed like excess complication. There are no right answers -- but I've tried to be consistent in use of ownership while keeping the interfaces as simple as possible. |
So, the fix for the Java test should be to set |
@DanBloomberg On the second thought, I would try to convince you that by default, pixaReplacePix() should be a CLONE operation. The fact that this test has been crashing for 3 years speaks for that. Nobody actually read the notes, even after their porgram crashes. But most importantly, consider C++ RAII when a Pix is allocated to replace a pix in Pixa. When the program goes out of the block where the original Pix was created, it will naturally destroy it, causing the same problem as we see in this small Java test. I don't see a scenario when replace would be called with a Pix that cannot be destroyed, or where calling such destroy would be counter-intuitive. |
Leptonica has patterns that are implemented consistently, and one of them is the ownership of the object by the container. Also, unnecessary copies of images should be avoided, as well as clones because they require pixDestroy() on both. To change the behavior of this function would unfortunately cause every existing invocation that is not crashing to have a memory leak! However, your point is interesting, and not something I've run up against because people usually do not use std::unique_ptr when allocating pix. If you do use such a pointer, with the current implementation you would need to call release() on your ptr to get a new pix ptr that is not "smart", and use that in the function call. So it's a judgment call. The best I can do now is to annotate the function with a caution not to use the content of a smart pointer, which by definition maintains ownership. Dan |
Note that the Java test that triggered this discussion does not use unique_ptr. The test goes like this:
For some strange reason, I will fix the Java test, and add your explanation to JavaDoc. |
fix JavaDoc enable and fix testPixaReplacePix see rmtheis#159 (comment)
fix JavaDoc enable and fix testPixaReplacePix see rmtheis#159 (comment)
@Robyer this is not a surprise, this crash caused by use-after-free bug did not happen for me in debug build, or when I ran this test single. It does not depend on version of Leptonica, because it is related to 'as designed' behavior of the library, see #159 (comment). |
@alexcohn You are right, it doesn't crash when running only the single test, but it crashes when I run all tests. Difference with my fork and current tess-two is that in my case it crashes in some other random test, but obviously this is the cause. So I'll use your fix too, thanks. |
Crash didn't happen when running the single test, but happened when running all the tests. See rmtheis/tess-two#159
Unfortunately, now the test produces a warning Same would have been reported for the pix, only there we don't have a I must say that I am in doubt about the cleanest way to deal with this issue. The easiest would be to remove The damage that such The big problem is that the JVM will trigger such I hope that @DanBloomberg will help us choose the best route. |
This has caused so much trouble for your java tests!
My suggestion is not to use the offending leptonica function in your tests.
(If you really want a replace pix in pixa function, implement it in java.)
…-- Dan
On Mon, Jul 22, 2019 at 5:21 AM Alex Cohn ***@***.***> wrote:
Unfortunately, now the test produces a warning Box was not terminated
using recycle(). It happens because the Java *Box.mRecycled* flag gets
out of sync with the native *refcount*.
Same would have been reported for the *pix*, only there we don't have a
finalize() override to let us worry.
I must say that I am in doubt about the cleanest way to deal with this
issue. The easiest would be to remove Box.finalize(). This introduces
another way to leak (native) memory, but it's not too bad, if you consider
that there are no similar checks for *Pix*.
The damage that such finalize() can cause, is worse. When Java decides
that an object must be finally destroyed, it will operate on the underlying
native object, which could be already deallocated (that's what would have
happened in this *testPixaReplacePix* case before my fix).
The big problem is that the JVM will trigger such finalize() at its own
discretion, most likely when it goes low on memory. This means that an
unexpected crash can easily pass the usual tests, and happen in production.
I hope that @DanBloomberg <https://github.com/DanBloomberg> will help us
choose the best route.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#159?email_source=notifications&email_token=AD7KMLHBQDUWNCVCIOFOYJLQAWJ5PA5CNFSM4CIOD372YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2PTPQQ#issuecomment-513750978>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AD7KMLGY2VPIJRKQLSRHC4LQAWJ5PANCNFSM4CIOD37Q>
.
|
@DanBloomberg my question now is not how to deal with this specific If |
These details are well above my pay grade :-) |
@rmtheis What do you think about this? Do you remember why @renard314 removed Px.finalize() ? |
This makes it consistent with Pix implementation, where finalizer was also removed in rmtheis/tess-two@44627d8 This removes warning "Box was not terminated using recycle()" which was wrongly raised in testPixaReplacePix() test (see rmtheis/tess-two#159 (comment)), but now requires that user don't forget to release his Box instances (same as with Pix).
I'm creating this issue to discuss why testPixaReplacePix crashes in the JNI code, as mentioned here: #157 (comment)
The code referenced is here:
tess-two/tess-two-test/src/com/googlecode/leptonica/android/test/PixaTest.java
Line 150 in a0e3b5e
I am working on finding out exactly how to fix this, but the preliminary testing show that commenting out the following two lines fixes the crash:
returnedPix.recycle(); returnedBox.recycle();
I believe these Pix and Box get recycled and destroyed before they should be, and then when the Pixa tries to recycle them internally, it crashes. I am not 100% sure on this, and plan to test more.
The text was updated successfully, but these errors were encountered: