[cairo] 'cairo_restore()' doesn't restore context in error case

Behdad Esfahbod behdad at behdad.org
Tue Feb 19 21:52:40 PST 2008


On Tue, 2008-02-19 at 23:00 +0200, Iosif Haidu wrote:

> I agree with you that my assumption might be wrong, but that's what
> happens when function names are not chose wisely (according with their
> implementation). If the 'cairo_restore()' function would have been
> named 'cairo_partial_restore()' instead, then I would have know that
> probably only some of the fields of 'cairo_t' structure will be
> restored and not all of them.

All the settings in cairo_t are restored except for the path.  But note
that I said all the *settings*.  Error status is not a setting.  You
cannot set it.

The point of save/restore is to let you set some stuff, draw, then
restore back to the previous settings.


> Do you consider that windows device context handle is not worth to be
> restored ? ('dc' member of 'cairo_win32_surface_t')

cairo_save()/cairo_restore() save/restore settings in cairo_t.  How is
that relevant to cairo_win32_surface_t?


>         > As I said on the bug, the this is a bug that should be fixed.  If the
>         > failing gdi functions are not fatal, this should be handled in cairo.
>         
>         > If the errors are fatal errors (say, real out-of-memory), cairo fails to
>         > produce the exact output that you asked it to, and that's an error.  And
>         > cairo's error handling model is to retain errors.  It's really that
>         > simple.  
>         
> 
> Well, the output of the error is that my application main window
> freezes and nothing is drawn anymore (I must close the application and
> start again). I would say that this error it's a critical one.

Ok, and I agreed that it's probably a bug that should be fixed.


> The problem why the main screen freezes is because the windows device
> context handle is not restored and somehow inside the library this is
> damaged.

What do you mean "not restored" and "damaged"?  I appreciate you trying
to analyze this, but the best way you can report a bug is to provide
code producing the bug, rather than insisting on what you think is a
proper fix.


> For an application that draws real time waveforms it would be
> acceptable to lose some drawings but not to freeze.
> Probably you didn't try to check the cairo library functions and try
> some scenario of 'what can happen if xxx windows gdi function fails
> for some unknown reason'. But I can give you some examples:

I don't have access to win32, so no, I didn't try.  But cairo on win32
is working for so many people out there.  Expecting that I try it and
see that it fails without providing me with any test code is not going
to work.


> a)
> File: cairo-win32-surface.c
> Function: static cairo_status_t _create_dc_and_bitmap(...)
> ...
> surface->dc = CreateCompatibleDC(original_dc);
> if (!surface->dc)
> goto FAIL;
> 
> surface->bitmap = CreateDIBSection (surface->dc,
>                             bitmap_info,
>                             DIB_RGB_COLORS,
>                             &bits,
>                             NULL, 0);
> if (!surface->bitmap)
> goto FAIL;
> ...
> surface->saved_dc_bitmap = SelectObject (surface->dc,
>                          surface->bitmap);
> if (!surface->saved_dc_bitmap)
> goto FAIL;
> ...
> FAIL:
>     status = _cairo_win32_print_gdi_error ("_create_dc_and_bitmap");
>     ...
>     if (surface->saved_dc_bitmap) {
>     SelectObject (surface->dc, surface->saved_dc_bitmap);
>     surface->saved_dc_bitmap = NULL;
>     }
>     
> In my situation, for unknown reason, the execution of
> 'CreateCompatibleDC()' fails. I'll let you to find out what happens in
> the FAIL section if 'CreateCompatibleDC()' or 'CreateDIBSection()'
> fails.

"for unknown reason", that's where you should investigate.


> b)
> File: cairo-win32-surface.c
> Function: static cairo_status_t
> _cairo_win32_surface_acquire_dest_image()
> ...
> if (GetClipBox (surface->dc, &clip_box) == ERROR)
> return _cairo_win32_print_gdi_error
> ("_cairo_win3_surface_acquire_dest_image");
> ...
> 
> The execution of 'GetClipBox()' sometimes fails.

Why?


> c)
> File: cairo-win32-surface.c
> Function: static cairo_int_status_t
> _cairo_win32_surface_set_clip_region()
> ...
> if (ExtSelectClipRgn (surface->dc, gdi_region, RGN_AND) == ERROR)
> status = _cairo_win32_print_gdi_error
> ("_cairo_win32_surface_set_clip_region");
> ..
> 
> The execution of 'ExtSelectClipRgn()' sometimes fails.

Same here.


> And here are the cairo api leading to this situation:
> 
>     cairo_save(pDcM);
>     cairo_move_to(pDcM, rBeginP.getX(), rBeginP.getY());
>     cairo_line_to(pDcM, rEndP.getX(), rEndP.getY());
>     cairo_stroke(pDcM);
>     cairo_restore(pDcM);
> 
> 
> Now, if in the first case it's clearly a bug to not check in the FAIL
> section if 'surface->saved_dc_bitmap' has a valid value (it has a
> garbage value != 0 and it goes inside the 'if' and that garbage value
> is selected in the surface device context) for the other two cases
> I'll let you to find out the real reason.

I can't.  But hopefully one of our win32 hackers will.  Now this is
starting to look like a real bug.  Can you please follow up with this
instead of the cairo_save()/restore() proposal?


> You could say that in the first case the bug can be fixed, so probably
> the problem will be also fixed. But fixing the 'cairo_restore()' to
> really restore what have been saved has also other benefits. For
> example such bugs will not affect anymore the main application (there
> are other mechanisms to catch such bugs if this is the intention of
> retaining the error code), the execution of a cairo_save/cairo_restore
> block will act like a transaction (transactions are a good mechanism
> to build a stable systems)

Making restore clear the error status doesn't not make it work more
robustly, no.  Transactions are a completely different beast.  Just
because the usage pattern looks similar doesn't mean these are the same
thing.

A transactional API would look like:

  cairo_begin()
  some drawing
  some more drawing
  cairo_rollback()

at which time none of the drawing between begin and rollback will end up
in the end result.  There's currently no way to achieve that in cairo
(push/pop_group is the closest you get).  As I said, save/restore is
really something different.  Accept it.


> > What is it good for if cairo tells you that everything is ok
> > but in reality it failed to draw some of the stuff you asked it to?
> 
> I never said that. I think you misunderstood my explanation or didn't
> pay attention enough on my previous e-mail. 

I totally understood you and paid more than enough attention.  I think
you are missing the point though: cairo_save/restore() is NOT what you
think it is.


> The developer is able to check every moment, inside of a
> 'cairo_save()/cairo_restore()' block if an error has occurred by
> calling 'cairo_status()' api function and act correspondingly.  What I
> have said was that is not necessary to keep the error but rather clear
> it after 'cairo_restore()' will finish it's execution. The only
> drawback of this fix is compatibility with previous versions. And yes,
> there might be applications that would rather like to miss some 
> drawings but instead restore the right device context handle.
[...]

I don't think discussing this further helps anyone.  I'll try to focus
on the bug at hand.


-- 
behdad
http://behdad.org/

"Those who would give up Essential Liberty to purchase a little
 Temporary Safety, deserve neither Liberty nor Safety."
        -- Benjamin Franklin, 1759



More information about the cairo mailing list