Is System.Drawing (libgdiplus) thread-safe?

View: New views
6 Messages — Rating Filter:   Alert me  

Is System.Drawing (libgdiplus) thread-safe?

by Alexander Shulgin :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi all,

Is System.Drawing by any means thread-safe?

Can I create some threads, create a Graphics object in each of them and
then work with it from within that thread?  Is this supposed to work or
am I doing something really stupid here?

I ask because I've noticed random crashes in a WinForms app on OS X (but
it happens on Linux too).  Most of the time it crashes in
System.Drawing.Graphics' DrawString or MeasureString methods and seems
to occur then a background worker thread is working in parallel with UI
thread.

I've tried to do a stress-test of System.Drawing in a sample
multi-threaded program.  See attached `test-multi-threaded-drawing.cs'.

On my Linux box it crashes all the time.  I get a wide variety of errors
from gdb stacktraces with SIGSEGV or SIGABRT in the end, to SIGILL with
.Net stack trace.

Uncommenting these lock {} lines in the ThreadProc helps, but not an
option for my real app, as there's simply no single place a lock could
be added.

I've also tried writing some code in plain C which links to libgdiplus
directly: see `threads-gdiplus.c'.  It happily crashes just like the C#
version.

My tests show that even using unsynchronized GdipGetImageGraphicsContext
/ GdipDeleteGraphics (no fonts stuff touched) can easily lead to crashes.

 From what I've seen, cairo seems to be thread-safe: see attached[1]
`cairo-multi-thread-text.c'.

Also, there's a few locking used around thread-unsafe fontconfig calls
in libgdiplus itself.  I didn't examined the whole code, so there's
possibly other places in it missing locking primitives.

I would appreciate any help on this subject!

--
Regards,
Alex
[1] originally found in the cairo bugzilla for a few-years-old bug; my
sligthly enhanced version

/* gmcs test-multi-threaded-drawing.cs -r:System.Drawing,System.Windows.Forms */
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace test {
        public class MainForm : Form {
                private static int threadCount = 0;

                private object consoleLock = new Object();
                private object hwndLock = new Object();

                public static void Main(string[] args) {
                        threadCount = args.Length == 0 ? 6 : int.Parse(args[0]);

                        Application.Run(new MainForm());
                }

                protected override void OnLoad(EventArgs e) {
                        base.OnLoad(e);

                        for (int i = 0; i < threadCount; ++i) {
                                Thread t = new Thread(ThreadProc);
                                t.Start(i);
                        }
                }

                void ThreadProc(object data) {
                        int threadnum = (int) data;

                        Random rnd = new Random((int) DateTime.Now.Ticks);
                        int count = rnd.Next(250, 1000);
                        lock (consoleLock) {
                                Console.WriteLine("thread{0} start: {1}", threadnum, count);
                        }

                        for (int i = 0; i < count; ++i) {
                                string str = CreateRandomString(rnd);

                                using (Graphics g = GetGraphicsForMeasurement()) {
                                        using (Font font = CreateRandomFont(rnd)) {
                                                //lock (hwndLock) {
                                                SizeF sz = g.MeasureString(str, font);

                                                using (Bitmap bmp = new Bitmap((int) sz.Width, (int) sz.Height)) {
                                                        using (Graphics gfx = Graphics.FromImage(bmp)) {
                                                                using (Brush b = Brushes.Red) {
                                                                        gfx.DrawString(str, font, b, new PointF(0f, 0f));
                                                                }
                                                        }
                                                }
                                                //}
                                        }
                                }
                        }

                        lock (consoleLock) {
                                Console.WriteLine("thread{0} done", threadnum);
                        }
                }

                string CreateRandomString(Random rnd) {
                        int len = 1 + rnd.Next(60);
                        StringBuilder sb = new StringBuilder(len);
                        for (int j = 0; j < len; ++j) {
                                int ch = (rnd.Next() & 1) == 1 ? 0x41 : 0x61; // 'A' or 'a'
                                sb.Append(Char.ConvertFromUtf32(ch + rnd.Next(26)));
                        }
                        return sb.ToString();
                }

                Graphics GetGraphicsForMeasurement() {
#if NO_GRAPHICS_FROM_HWND
                        Bitmap tmp = new Bitmap(1, 1);
                        return Graphics.FromImage(tmp);
#else
                        Graphics g;
                        lock (hwndLock) {
                                g = Graphics.FromHwnd(this.Handle);
                        }
                        return g;
#endif
                }

                Font CreateRandomFont(Random rnd) {
                        return new Font("Sans", (int) (8 + rnd.NextDouble()*10));
                }
        }
}

/* gcc `pkg-config --cflags --libs cairo` -lpthread multi-thread-text.c -o multi-thread-text */

/*
 * Copyright © 2005 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Red Hat, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. Red Hat, Inc. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Carl D. Worth <cworth@...>
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <cairo.h>
#include <pthread.h>

static void *
start (void *closure)
{
    int i;

    for (i = 0; i < 1000; ++i) {
      cairo_surface_t *surface;
      cairo_t *cr;

      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
      cr = cairo_create (surface);

      cairo_move_to (cr, 10, 10);

      cairo_set_font_size (cr, 10);
      cairo_show_text (cr, "Hello world.\n");

      cairo_set_font_size (cr, 9);
      cairo_show_text (cr, "Hello world.\n");

      cairo_set_font_size (cr, 8);
      cairo_show_text (cr, "Hello world.\n");

      cairo_destroy (cr);
      cairo_surface_destroy (surface);
    }

    return NULL;
}

int
main (int argc, char *argv[0])
{
    int i, num_threads;
    pthread_t *pthread;

    if (argc > 1) {
        num_threads = atoi (argv[1]);
    } else {
        num_threads = 6;
        printf ("Running with default value of %d threads.\n"
                "To change, call: %s <number_of_threads>\n",
                num_threads, argv[0]);
    }

    pthread = malloc (num_threads * sizeof (pthread_t));
    assert (pthread != NULL);

    for (i = 0; i < num_threads; i++)
        pthread_create (&pthread[i], NULL, start, NULL);

    for (i = 0; i < num_threads; i++) {
        pthread_join (pthread[i], NULL);
        printf("joined thread%d\n", i);
    }

    return 0;
}

/* gcc threads-gdiplus.c `pkg-config --cflags glib-2.0` -Wall -lgdiplus -lpthread */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <gdiplus/GdiPlusFlat.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void
fatal(char const* func, GpStatus err)
{
  fprintf(stderr, "%s: %d\n", func, err);
  exit(1);
}

static void*
start(void* closure)
{
  GpStatus err;

  GpBitmap* img;
  int width = 128;
  int height = 32;
  int stride = width*4;

  GpGraphics* gfx;

  GpFontFamily* family;
  GpFont* font;

  WCHAR const str[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','!'};
  size_t len = sizeof(str)/sizeof(WCHAR);
  RectF rect;
  RectF bounds;

  int i;

  for (i = 0; i < 1000; ++i) {

    err = GdipCreateBitmapFromScan0(width, height, stride, PixelFormat32bppARGB, /* scan0 = */ NULL, &img);
    if (err != Ok)
      fatal("GdipCreateBitmapFromScan0", err);

    //assert(pthread_mutex_lock(&mutex) == 0);

    err = GdipGetImageGraphicsContext(img, &gfx);
    if (err != Ok)
      fatal("GdipGetImageGraphicsContext", err);

    //assert(pthread_mutex_unlock(&mutex) == 0);


    err = GdipGetGenericFontFamilySansSerif(&family);
    if (err != Ok)
      fatal("GdipGetGenericFontFamilySansSerif", err);

    err = GdipCreateFont(family, 12.0, FontStyleRegular, UnitPoint, &font);
    if (err != Ok)
      fatal("GdipCreateFont", err);



    rect.X = 0;
    rect.Y = 0;
    rect.Width = width;
    rect.Height = height;

    //assert(pthread_mutex_lock(&mutex) == 0);

    err = GdipMeasureString(gfx, str, len, font, &rect, /* format = */ NULL, &bounds,
                            /* codepoints = */ NULL, /* lines = */ NULL);
    if (err != Ok)
      fatal("GdipMeasureString", err);

    //assert(pthread_mutex_unlock(&mutex) == 0);


    err = GdipDeleteFont(font);
    if (err != Ok)
      fatal("GdipDeleteFont", err);



    err = GdipDeleteGraphics(gfx);
    if (err != Ok)
      fatal("GdipDeleteGraphics", err);


    err = GdipDisposeImage(img);
    if (err != Ok)
      fatal("GdipDisposeImage", err);

  }

  return NULL;
}

int
main(int argc, char *argv[0])
{
  int i, num_threads;
  pthread_t* pthread;

  GpStatus err;
  ULONG_PTR gdiptok;
  GdiplusStartupInput gdipinput;
  GdiplusStartupOutput gdipoutput;

  err = GdiplusStartup(&gdiptok, &gdipinput, &gdipoutput);
  if (err != Ok)
    fatal("GdiplusStartup", err);


  if (argc > 1) {
    num_threads = atoi(argv[1]);
  } else {
    num_threads = 6;
    printf("Running with default value of %d threads.\n"
           "To change, call: %s <number_of_threads>\n",
           num_threads, argv[0]);
  }

  pthread = malloc(num_threads * sizeof(pthread_t));
  assert(pthread != NULL);

  for (i = 0; i < num_threads; i++)
    pthread_create(&pthread[i], NULL, start, NULL);

  for (i = 0; i < num_threads; i++) {
    pthread_join(pthread[i], NULL);
    printf("joined thread%d\n", i);
  }

  pthread_mutex_destroy(&mutex);

  return 0;
}

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx

Re: [Mono-winforms-list] Is System.Drawing (libgdiplus) thread-safe?

by Alexander Shulgin :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Alex Shulgin wrote:
> Hi all,
>
> Is System.Drawing by any means thread-safe?
>
> Can I create some threads, create a Graphics object in each of them and
> then work with it from within that thread?  Is this supposed to work or
> am I doing something really stupid here?

Oh, forgot to mention that I've tested this on both 2.4 and latest
available 2.6.  The results are the same.

--
Alex

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx

Re: [Mono-winforms-list] Is System.Drawing (libgdiplus) thread-safe?

by Sebastien Pouliot-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Wed, 2009-10-28 at 19:37 +0200, Alex Shulgin wrote:
> Hi all,
>
> Is System.Drawing by any means thread-safe?

No, like most of the .net framework, i.e.

        <quote>Any public static (Shared in Visual Basic) members of
        this type are thread safe. Any instance members are not
        guaranteed to be thread safe.</quote>

>
> Can I create some threads, create a Graphics object in each of them and
> then work with it from within that thread?  

Yes, you can (or it's a bug). However you also need to make sure you're
not using the Graphic instance (and other created objects) only from a
single (and original) thread.

> Is this supposed to work or
> am I doing something really stupid here?
>
> I ask because I've noticed random crashes in a WinForms app on OS X (but
> it happens on Linux too).  Most of the time it crashes in
> System.Drawing.Graphics' DrawString or MeasureString methods and seems
> to occur then a background worker thread is working in parallel with UI
> thread.
>
> I've tried to do a stress-test of System.Drawing in a sample
> multi-threaded program.  See attached `test-multi-threaded-drawing.cs'.

Please open a bug report on bugzilla.novell.com and attach your test
case.

> On my Linux box it crashes all the time.  I get a wide variety of errors
> from gdb stacktraces with SIGSEGV or SIGABRT in the end, to SIGILL with
> .Net stack trace.
>
> Uncommenting these lock {} lines in the ThreadProc helps, but not an
> option for my real app, as there's simply no single place a lock could
> be added.
>
> I've also tried writing some code in plain C which links to libgdiplus
> directly: see `threads-gdiplus.c'.  It happily crashes just like the C#
> version.

Most of System.Drawing is only a small wrapper abound libgdiplus (or GDI
+ under Windows). Any bug will likely be inside libgdiplus so it's
"normal" (i.e. expected) that you get the same behavior.

> My tests show that even using unsynchronized GdipGetImageGraphicsContext
> / GdipDeleteGraphics (no fonts stuff touched) can easily lead to crashes.
>
>  From what I've seen, cairo seems to be thread-safe: see attached[1]
> `cairo-multi-thread-text.c'.

It's thread-safe as long as everything is created and executed in the
same thread. Otherwise it would not be safe-thread (just like .net and
most libraries are).

> Also, there's a few locking used around thread-unsafe fontconfig calls
> in libgdiplus itself.  I didn't examined the whole code, so there's
> possibly other places in it missing locking primitives.

Yep. Font-related code (and older, pre-1.4, versions of Cairo) had quite
a few problems (solved with the locking code). Other than that SWF and
ASP.NET were the big clients for SD/libgdiplus and, for SWF,
multithreading was not an issue at all.

> I would appreciate any help on this subject!
>
> --
> Regards,
> Alex
> [1] originally found in the cairo bugzilla for a few-years-old bug; my
> sligthly enhanced version
> _______________________________________________
> Mono-winforms-list maillist  -  Mono-winforms-list@...
> http://lists.ximian.com/mailman/listinfo/mono-winforms-list

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx

Re: [Mono-dev] [Mono-winforms-list] Is System.Drawing (libgdiplus) thread-safe?

by Sebastien Pouliot-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Wed, 2009-10-28 at 19:47 +0200, Alex Shulgin wrote:

> Alex Shulgin wrote:
> > Hi all,
> >
> > Is System.Drawing by any means thread-safe?
> >
> > Can I create some threads, create a Graphics object in each of them and
> > then work with it from within that thread?  Is this supposed to work or
> > am I doing something really stupid here?
>
> Oh, forgot to mention that I've tested this on both 2.4 and latest
> available 2.6.  The results are the same.

There has not been many changes between 2.4 and 2.6 wrt
System.Drawing.dll and libgdiplus (i.e. minor maintenance).

Most graphic stuff (and time) now happens in Moonlight...

Sebastien

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx

Re: [Mono-dev] Is System.Drawing (libgdiplus) thread-safe?

by Miguel de Icaza-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hello,

     It is thread safe for instances of objects, but you can not mix  
objects that need to interact with the windowing system with calls  
made in separate threads.

     The simple solution is to make sure that anything that interacts  
with the GUI uses Control.Invoke.

On Oct 28, 2009, at 1:37 PM, Alex Shulgin wrote:

> Hi all,
>
> Is System.Drawing by any means thread-safe?
>
> Can I create some threads, create a Graphics object in each of them  
> and then work with it from within that thread?  Is this supposed to  
> work or am I doing something really stupid here?
>
> I ask because I've noticed random crashes in a WinForms app on OS X  
> (but it happens on Linux too).  Most of the time it crashes in  
> System.Drawing.Graphics' DrawString or MeasureString methods and  
> seems to occur then a background worker thread is working in  
> parallel with UI thread.
>
> I've tried to do a stress-test of System.Drawing in a sample multi-
> threaded program.  See attached `test-multi-threaded-drawing.cs'.
>
> On my Linux box it crashes all the time.  I get a wide variety of  
> errors from gdb stacktraces with SIGSEGV or SIGABRT in the end, to  
> SIGILL with .Net stack trace.
>
> Uncommenting these lock {} lines in the ThreadProc helps, but not an  
> option for my real app, as there's simply no single place a lock  
> could be added.
>
> I've also tried writing some code in plain C which links to  
> libgdiplus directly: see `threads-gdiplus.c'.  It happily crashes  
> just like the C# version.
>
> My tests show that even using unsynchronized  
> GdipGetImageGraphicsContext / GdipDeleteGraphics (no fonts stuff  
> touched) can easily lead to crashes.
>
> From what I've seen, cairo seems to be thread-safe: see attached[1]  
> `cairo-multi-thread-text.c'.
>
> Also, there's a few locking used around thread-unsafe fontconfig  
> calls in libgdiplus itself.  I didn't examined the whole code, so  
> there's possibly other places in it missing locking primitives.
>
> I would appreciate any help on this subject!
>
> --
> Regards,
> Alex
> [1] originally found in the cairo bugzilla for a few-years-old bug;  
> my sligthly enhanced version
> /* gmcs test-multi-threaded-drawing.cs -
> r:System.Drawing,System.Windows.Forms */
> using System;
> using System.Text;
> using System.Drawing;
> using System.Windows.Forms;
> using System.Threading;
>
> namespace test {
> public class MainForm : Form {
> private static int threadCount = 0;
>
> private object consoleLock = new Object();
> private object hwndLock = new Object();
>
> public static void Main(string[] args) {
> threadCount = args.Length == 0 ? 6 : int.Parse(args[0]);
>
> Application.Run(new MainForm());
> }
>
> protected override void OnLoad(EventArgs e) {
> base.OnLoad(e);
>
> for (int i = 0; i < threadCount; ++i) {
> Thread t = new Thread(ThreadProc);
> t.Start(i);
> }
> }
>
> void ThreadProc(object data) {
> int threadnum = (int) data;
>
> Random rnd = new Random((int) DateTime.Now.Ticks);
> int count = rnd.Next(250, 1000);
> lock (consoleLock) {
> Console.WriteLine("thread{0} start: {1}", threadnum, count);
> }
>
> for (int i = 0; i < count; ++i) {
> string str = CreateRandomString(rnd);
>
> using (Graphics g = GetGraphicsForMeasurement()) {
> using (Font font = CreateRandomFont(rnd)) {
> //lock (hwndLock) {
> SizeF sz = g.MeasureString(str, font);
>
> using (Bitmap bmp = new Bitmap((int) sz.Width, (int)  
> sz.Height)) {
> using (Graphics gfx = Graphics.FromImage(bmp)) {
> using (Brush b = Brushes.Red) {
> gfx.DrawString(str, font, b, new PointF(0f, 0f));
> }
> }
> }
> //}
> }
> }
> }
>
> lock (consoleLock) {
> Console.WriteLine("thread{0} done", threadnum);
> }
> }
>
> string CreateRandomString(Random rnd) {
> int len = 1 + rnd.Next(60);
> StringBuilder sb = new StringBuilder(len);
> for (int j = 0; j < len; ++j) {
> int ch = (rnd.Next() & 1) == 1 ? 0x41 : 0x61; // 'A' or 'a'
> sb.Append(Char.ConvertFromUtf32(ch + rnd.Next(26)));
> }
> return sb.ToString();
> }
>
> Graphics GetGraphicsForMeasurement() {
> #if NO_GRAPHICS_FROM_HWND
> Bitmap tmp = new Bitmap(1, 1);
> return Graphics.FromImage(tmp);
> #else
> Graphics g;
> lock (hwndLock) {
> g = Graphics.FromHwnd(this.Handle);
> }
> return g;
> #endif
> }
>
> Font CreateRandomFont(Random rnd) {
> return new Font("Sans", (int) (8 + rnd.NextDouble()*10));
> }
> }
> }
> /* gcc `pkg-config --cflags --libs cairo` -lpthread multi-thread-
> text.c -o multi-thread-text */
>
> /*
> * Copyright © 2005 Red Hat, Inc.
> *
> * Permission to use, copy, modify, distribute, and sell this software
> * and its documentation for any purpose is hereby granted without
> * fee, provided that the above copyright notice appear in all copies
> * and that both that copyright notice and this permission notice
> * appear in supporting documentation, and that the name of
> * Red Hat, Inc. not be used in advertising or publicity pertaining to
> * distribution of the software without specific, written prior
> * permission. Red Hat, Inc. makes no representations about the
> * suitability of this software for any purpose.  It is provided "as
> * is" without express or implied warranty.
> *
> * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
> * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
> * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
> * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
> * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
> * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
> * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> *
> * Author: Carl D. Worth <cworth@...>
> */
>
> #include <assert.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <cairo.h>
> #include <pthread.h>
>
> static void *
> start (void *closure)
> {
>    int i;
>
>    for (i = 0; i < 1000; ++i) {
>      cairo_surface_t *surface;
>      cairo_t *cr;
>
>      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100,  
> 100);
>      cr = cairo_create (surface);
>
>      cairo_move_to (cr, 10, 10);
>
>      cairo_set_font_size (cr, 10);
>      cairo_show_text (cr, "Hello world.\n");
>
>      cairo_set_font_size (cr, 9);
>      cairo_show_text (cr, "Hello world.\n");
>
>      cairo_set_font_size (cr, 8);
>      cairo_show_text (cr, "Hello world.\n");
>
>      cairo_destroy (cr);
>      cairo_surface_destroy (surface);
>    }
>
>    return NULL;
> }
>
> int
> main (int argc, char *argv[0])
> {
>    int i, num_threads;
>    pthread_t *pthread;
>
>    if (argc > 1) {
> num_threads = atoi (argv[1]);
>    } else {
> num_threads = 6;
> printf ("Running with default value of %d threads.\n"
> "To change, call: %s <number_of_threads>\n",
> num_threads, argv[0]);
>    }
>
>    pthread = malloc (num_threads * sizeof (pthread_t));
>    assert (pthread != NULL);
>
>    for (i = 0; i < num_threads; i++)
> pthread_create (&pthread[i], NULL, start, NULL);
>
>    for (i = 0; i < num_threads; i++) {
> pthread_join (pthread[i], NULL);
> printf("joined thread%d\n", i);
>    }
>
>    return 0;
> }
> /* gcc threads-gdiplus.c `pkg-config --cflags glib-2.0` -Wall -
> lgdiplus -lpthread */
>
> #include <assert.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <wchar.h>
>
> #include <gdiplus/GdiPlusFlat.h>
> #include <pthread.h>
>
> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
>
> void
> fatal(char const* func, GpStatus err)
> {
>  fprintf(stderr, "%s: %d\n", func, err);
>  exit(1);
> }
>
> static void*
> start(void* closure)
> {
>  GpStatus err;
>
>  GpBitmap* img;
>  int width = 128;
>  int height = 32;
>  int stride = width*4;
>
>  GpGraphics* gfx;
>
>  GpFontFamily* family;
>  GpFont* font;
>
>  WCHAR const str[] = {'H','e','l','l','o',',','  
> ','W','o','r','l','d','!'};
>  size_t len = sizeof(str)/sizeof(WCHAR);
>  RectF rect;
>  RectF bounds;
>
>  int i;
>
>  for (i = 0; i < 1000; ++i) {
>
>    err = GdipCreateBitmapFromScan0(width, height, stride,  
> PixelFormat32bppARGB, /* scan0 = */ NULL, &img);
>    if (err != Ok)
>      fatal("GdipCreateBitmapFromScan0", err);
>
>    //assert(pthread_mutex_lock(&mutex) == 0);
>
>    err = GdipGetImageGraphicsContext(img, &gfx);
>    if (err != Ok)
>      fatal("GdipGetImageGraphicsContext", err);
>
>    //assert(pthread_mutex_unlock(&mutex) == 0);
>
>
>    err = GdipGetGenericFontFamilySansSerif(&family);
>    if (err != Ok)
>      fatal("GdipGetGenericFontFamilySansSerif", err);
>
>    err = GdipCreateFont(family, 12.0, FontStyleRegular, UnitPoint,  
> &font);
>    if (err != Ok)
>      fatal("GdipCreateFont", err);
>
>
>
>    rect.X = 0;
>    rect.Y = 0;
>    rect.Width = width;
>    rect.Height = height;
>
>    //assert(pthread_mutex_lock(&mutex) == 0);
>
>    err = GdipMeasureString(gfx, str, len, font, &rect, /* format =  
> */ NULL, &bounds,
>                            /* codepoints = */ NULL, /* lines = */  
> NULL);
>    if (err != Ok)
>      fatal("GdipMeasureString", err);
>
>    //assert(pthread_mutex_unlock(&mutex) == 0);
>
>
>    err = GdipDeleteFont(font);
>    if (err != Ok)
>      fatal("GdipDeleteFont", err);
>
>
>
>    err = GdipDeleteGraphics(gfx);
>    if (err != Ok)
>      fatal("GdipDeleteGraphics", err);
>
>
>    err = GdipDisposeImage(img);
>    if (err != Ok)
>      fatal("GdipDisposeImage", err);
>
>  }
>
>  return NULL;
> }
>
> int
> main(int argc, char *argv[0])
> {
>  int i, num_threads;
>  pthread_t* pthread;
>
>  GpStatus err;
>  ULONG_PTR gdiptok;
>  GdiplusStartupInput gdipinput;
>  GdiplusStartupOutput gdipoutput;
>
>  err = GdiplusStartup(&gdiptok, &gdipinput, &gdipoutput);
>  if (err != Ok)
>    fatal("GdiplusStartup", err);
>
>
>  if (argc > 1) {
>    num_threads = atoi(argv[1]);
>  } else {
>    num_threads = 6;
>    printf("Running with default value of %d threads.\n"
>           "To change, call: %s <number_of_threads>\n",
>           num_threads, argv[0]);
>  }
>
>  pthread = malloc(num_threads * sizeof(pthread_t));
>  assert(pthread != NULL);
>
>  for (i = 0; i < num_threads; i++)
>    pthread_create(&pthread[i], NULL, start, NULL);
>
>  for (i = 0; i < num_threads; i++) {
>    pthread_join(pthread[i], NULL);
>    printf("joined thread%d\n", i);
>  }
>
>  pthread_mutex_destroy(&mutex);
>
>  return 0;
> }
> _______________________________________________
> Mono-devel-list mailing list
> Mono-devel-list@...
> http://lists.ximian.com/mailman/listinfo/mono-devel-list

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx

Re: [Mono-winforms-list] Is System.Drawing (libgdiplus) thread-safe?

by Alexander Shulgin :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Sebastien Pouliot wrote:
>
> Please open a bug report on bugzilla.novell.com and attach your test
> case.

https://bugzilla.novell.com/show_bug.cgi?id=551009

>> On my Linux box it crashes all the time.  I get a wide variety of errors
>> from gdb stacktraces with SIGSEGV or SIGABRT in the end, to SIGILL with
>> .Net stack trace.
>>
>> Uncommenting these lock {} lines in the ThreadProc helps, but not an
>> option for my real app, as there's simply no single place a lock could
>> be added.
>>
>> I've also tried writing some code in plain C which links to libgdiplus
>> directly: see `threads-gdiplus.c'.  It happily crashes just like the C#
>> version.
>
> Most of System.Drawing is only a small wrapper abound libgdiplus (or GDI
> + under Windows). Any bug will likely be inside libgdiplus so it's
> "normal" (i.e. expected) that you get the same behavior.

Yes, it's what I expected.  Just thought it might be helpful in
debugging the problem.  Attached this to the bug too.

>> My tests show that even using unsynchronized GdipGetImageGraphicsContext
>> / GdipDeleteGraphics (no fonts stuff touched) can easily lead to crashes.
>>
>>  From what I've seen, cairo seems to be thread-safe: see attached[1]
>> `cairo-multi-thread-text.c'.
>
> It's thread-safe as long as everything is created and executed in the
> same thread. Otherwise it would not be safe-thread (just like .net and
> most libraries are).

This is actually what I meant by thread-safe here. :)

I'd expect any library not to crash under similar circumstances as long
as it doesn't use global variables or, if so, does ensure proper locking
around them.

--
Regards,
Alex

_______________________________________________
Mono-osx mailing list
Mono-osx@...
http://lists.ximian.com/mailman/listinfo/mono-osx