On May 12, 2018, at 3:25 AM, Gregor Mückl <[email protected]> wrote:
> I need to reply to myself here because I finally found a good explanation
> of why this sometimes works (and I turned out to be lucky) and sometimes
> fails miserably. It's on Raymond Chen's blog of all places:
> 
> https://blogs.msdn.microsoft.com/oldnewthing/20161209-00/?p=94905

Thanks for the link!

It's insidious. Not only does it "sometimes work / sometimes fail" on Windows, 
but it will *always* work for developers who work on Linux or OSX, only to find 
that when other people use their code on Windows, it will misbehave.


> So OIIO really must change in order to cope with all the different
> possibilities how program heaps can be structured on Windows.

Yes, though that's not exactly how I'd characterize the main point of this 
patch.

We've actually been aware of this issue all along, and that's why there was 
always an ImageInput::destroy() method -- because if an app calls 'delete' 
itself, the free will be on the app side but the allocation was in 
libOpenImageIO (different side of the dll boundary, so potentially different 
heap). But it had always been the case that an app programmer could easily 
forget to do this, call delete instead of II::destroy(), and make trouble. And 
in the process of doing this work (but not the impetus for it) I realized that 
destroy() would still fail for *custom* ImageInputs, which might not be 
implemented in OpenImageIO.dll (where destroy lives).

Fully managed pointers with deleters (that execute the free in the same dll 
that did the allocation) is a more robust fix.

The real impetus for this change, though, was just to fully clean up all the 
potential errors that could easily happen when we return a raw pointer:
App never destroys the pointer (leak) -- can no longer happen.
App accidentally calls delete instead of ImageInput::destroy() (it will 
silently work on Linux/OSX, but subtly break on Windows because the free can 
happen on the wrong side of the dll boundary) -- can no longer happen.
App accidentally calls free() instead of delete/destroy (ouch!) -- can no 
longer happen.
App function that created the ImageInput passes it to another subroutine... is 
it transferring ownership? Who is responsible for freeing? Now it's clear -- if 
the unique_ptr is moved to you, you are the new owner, but if you just get a 
raw pointer, somebody else owns the resource and you are just a visitor.
It also just simplifies code and documentation. Something that previously 
looked like

    ImageInput *in = ImageInput::open ("foo.tif");
    if (! in) {
        return;
    }
    if (in->spec makes it clear this isn't the image you want) {
        ImageInput::destroy (in);   // <---------
        return;
    }
    ok = in->read_image(...);
    if (! ok) {
        ... more error processing ..    
        ImageInput::destroy (in);   // <---------
        return;
    }
    my_pixel_processing (...);
    ImageInput::destroy (in);   // <---------

It's exhausting to have to find all the places where that raw pointer might be 
dangling and be sure to destroy it. The new code equivalent is:

    auto in = ImageInput::open ("foo.tif");
    if (! in)
        return;
    if (in->spec makes it clear this isn't the image you want)
        return;
    ok = in->read_image(...);
    if (! ok) {
        ... more error processing ..    
        return;
    }
    my_pixel_processing (...);

So much cleaner, shorter and clearer examples for the user manual, less chance 
of bugs. 

And did you spot the bug still in the original? What happens if 
my_pixel_processing() throws an exception that doesn't get caught until a 
couple levels up the call stack? That ImageInput will never be freed at all. 
But it will the new way, as soon as execution leaves the scope where 'in' was 
declared.

--
Larry Gritz
[email protected]




_______________________________________________
Oiio-dev mailing list
[email protected]
http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org

Reply via email to