avoid re-entrancy into giflib using the mutex API
authorTony Cook <tony@develop-help.com>
Mon, 15 Oct 2012 12:23:22 +0000 (23:23 +1100)
committerTony Cook <tony@develop-help.com>
Sat, 24 Nov 2012 03:59:26 +0000 (14:59 +1100)
GIF/GIF.pm
GIF/GIF.xs
GIF/imgif.c
GIF/imgif.h
bench/.gitignore
bench/gifthread.pl [new file with mode: 0644]

index 5d92f7a..05fb1f9 100644 (file)
@@ -4,7 +4,7 @@ use Imager;
 use vars qw($VERSION @ISA);
 
 BEGIN {
-  $VERSION = "0.85";
+  $VERSION = "0.86";
 
   require XSLoader;
   XSLoader::load('Imager::File::GIF', $VERSION);
index 75687b8..7641b8c 100644 (file)
@@ -147,3 +147,4 @@ i_readgif_multi_wiol(ig)
 BOOT:
        PERL_INITIALIZE_IMAGER_CALLBACKS;
        PERL_INITIALIZE_IMAGER_PERL_CALLBACKS;
+       i_init_gif();
index 8dfe834..9b6e6b1 100644 (file)
@@ -66,7 +66,8 @@ functionality with giflib3.
 #define myGifError(gif) ((gif)->Error)
 #define MakeMapObject GifMakeMapObject
 #define FreeMapObject GifFreeMapObject
-
+#define gif_mutex_lock(mutex)
+#define gif_mutex_unlock(mutex)
 #else
 #define PRE_SET_VERSION
 static GifFileType *
@@ -86,6 +87,8 @@ myEGifOpen(void *userPtr, OutputFunc outputFunc, int *error) {
   return result;
 }
 #define myGifError(gif) GifLastError()
+#define gif_mutex_lock(mutex) i_mutex_lock(mutex)
+#define gif_mutex_unlock(mutex) i_mutex_unlock(mutex)
 
 #endif
 
@@ -98,6 +101,17 @@ static const int
   InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
   InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
 
+#if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5
+static i_mutex_t mutex;
+#endif
+
+void
+i_init_gif(void) {
+#if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5
+  mutex = i_mutex_new();
+#endif
+}
+
 static
 void
 i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
@@ -877,6 +891,9 @@ i_img **
 i_readgif_multi_wiol(io_glue *ig, int *count) {
   GifFileType *GifFile;
   int gif_error;
+  i_img **result;
+
+  gif_mutex_lock(mutex);
 
   i_clear_error();
   
@@ -884,10 +901,15 @@ i_readgif_multi_wiol(io_glue *ig, int *count) {
     gif_push_error(gif_error);
     i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
+    gif_mutex_unlock(mutex);
     return NULL;
   }
     
-  return i_readgif_multi_low(GifFile, count, -1);
+  result = i_readgif_multi_low(GifFile, count, -1);
+
+  gif_mutex_unlock(mutex);
+
+  return result;
 }
 
 static int
@@ -901,6 +923,9 @@ i_img *
 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
   GifFileType *GifFile;
   int gif_error;
+  i_img *result;
+
+  gif_mutex_lock(mutex);
 
   i_clear_error();
 
@@ -908,10 +933,15 @@ i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
     gif_push_error(gif_error);
     i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+    gif_mutex_unlock(mutex);
     return NULL;
   }
     
-  return i_readgif_low(GifFile, color_table, colors);
+  result = i_readgif_low(GifFile, color_table, colors);
+
+  gif_mutex_unlock(mutex);
+
+  return result;
 }
 
 /*
@@ -957,6 +987,7 @@ i_img *
 i_readgif_single_wiol(io_glue *ig, int page) {
   GifFileType *GifFile;
   int gif_error;
+  i_img *result;
 
   i_clear_error();
   if (page < 0) {
@@ -964,15 +995,21 @@ i_readgif_single_wiol(io_glue *ig, int page) {
     return NULL;
   }
 
+  gif_mutex_lock(mutex);
 
   if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
     gif_push_error(gif_error);
     i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+    gif_mutex_unlock(mutex);
     return NULL;
   }
     
-  return i_readgif_single_low(GifFile, page);
+  result = i_readgif_single_low(GifFile, page);
+
+  gif_mutex_unlock(mutex);
+
+  return result;
 }
 
 /*
@@ -1830,6 +1867,8 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
   int gif_error;
   int result;
 
+  gif_mutex_lock(mutex);
+
   i_clear_error();
 
 #ifdef PRE_SET_VERSION
@@ -1840,6 +1879,7 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
     gif_push_error(gif_error);
     i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
+    gif_mutex_unlock(mutex);
     return 0;
   }
   
@@ -1849,6 +1889,8 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
 
   result = i_writegif_low(quant, GifFile, imgs, count);
   
+  gif_mutex_unlock(mutex);
+
   if (i_io_close(ig))
     return 0;
   
@@ -1970,6 +2012,8 @@ gif_push_error(int code) {
 
 Arnar M. Hrafnkelsson, addi@umich.edu
 
+Tony Cook <tonyc@cpan.org>
+
 =head1 SEE ALSO
 
 perl(1), Imager(3)
index ca92af5..9a92e8c 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "imext.h"
 
+void i_init_gif(void);
 double i_giflib_version(void);
 i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours);
 i_img *i_readgif_single_wiol(io_glue *ig, int page);
index 4865f40..595be54 100644 (file)
@@ -1 +1,2 @@
 /largish.tif
+/largish.gif
diff --git a/bench/gifthread.pl b/bench/gifthread.pl
new file mode 100644 (file)
index 0000000..e22a242
--- /dev/null
@@ -0,0 +1,55 @@
+#!perl -w
+use strict;
+use threads;
+use Imager;
+
+++$|;
+Imager->preload;
+
+# as a TIFF this file is large, build it from largeish.jpg if it
+# doesn't exist
+unless (-f "bench/largish.gif") {
+  my $im = Imager->new(file => "bench/largish.jpg")
+    or die "Cannot read bench/largish.jpg:", Imager->errstr;
+  $im->write(file => "bench/largish.gif")
+    or die "Cannot write bench/largish.gif:", $im->errstr;
+}
+
+my @tests =
+  (
+   [ "bench/largish.gif", "" ],
+   [ "GIF/testimg/scale.gif", "" ],
+   [ "GIF/testimg/nocmap.gif", "Image does not have a local or a global color map" ],
+  );
+
+my @threads;
+my $name = "A";
+for my $test (@tests) {
+  push @threads,
+    threads->create
+      (
+       sub  {
+        my ($file, $result, $name) = @_;
+        for (1 .. 100000) {
+          print $name;
+          my $im = Imager->new(file => $file);
+          if ($result) {
+            $im and die "Expected error from $file, got image";
+            Imager->errstr eq $result
+              or die "Expected error '$result', got '",Imager->errstr, "'"
+          }
+          else {
+            $im or die "Expected image got error '", Imager->errstr, "'";
+          }
+        }
+        return;
+       },
+       @$test,
+       $name
+      );
+  ++$name;
+}
+
+for my $t (@threads) {
+  $t->join();
+}