Permalink
Browse files

improved thread safety for Imager

  • Loading branch information...
2 parents 41c938e + 7d99bed commit 02c63035876648ef730cbde539cd75c53236a032 @tonycoz committed Sep 11, 2012
Showing with 3,420 additions and 1,091 deletions.
  1. +15 −0 Changes
  2. +5 −0 FT2/Changes
  3. +50 −1 FT2/FT2.pm
  4. +1 −0 FT2/FT2.xs
  5. +1 −0 FT2/MANIFEST
  6. +1 −0 FT2/Makefile.PL
  7. +60 −14 FT2/freetyp2.c
  8. +1 −1 FT2/imft2.h
  9. +59 −0 FT2/t/t20thread.t
  10. +1 −1 GIF/GIF.pm
  11. +1 −0 GIF/GIF.xs
  12. +41 −4 GIF/imgif.c
  13. +1 −0 GIF/imgif.h
  14. +6 −9 Imager.pm
  15. +94 −1 Imager.xs
  16. +1 −1 JPEG/JPEG.pm
  17. +10 −46 JPEG/imjpeg.c
  18. +9 −1 MANIFEST
  19. +29 −4 Makefile.PL
  20. +9 −0 T1/Changes
  21. +1 −0 T1/MANIFEST
  22. +64 −27 T1/T1.pm
  23. +35 −27 T1/T1.xs
  24. +194 −50 T1/imt1.c
  25. +13 −11 T1/imt1.h
  26. +54 −27 T1/t/t10type1.t
  27. +1 −0 T1/typemap
  28. +6 −0 TIFF/Changes
  29. +1 −1 TIFF/TIFF.pm
  30. +1 −0 TIFF/TIFF.xs
  31. +204 −15 TIFF/imtiff.c
  32. +1 −0 TIFF/imtiff.h
  33. +3 −3 apidocs.perl
  34. BIN bench/largish.png
  35. BIN bench/largish.tif
  36. +46 −0 bench/tifthread.pl
  37. +39 −20 bmp.c
  38. +284 −0 context.c
  39. +5 −3 conv.im
  40. +7 −5 convert.im
  41. +4 −2 datatypes.c
  42. +79 −24 draw.c
  43. +8 −4 dynaload.c
  44. +68 −124 error.c
  45. +28 −1 ext.h
  46. +1 −0 fills.c
  47. +31 −19 filters.im
  48. +5 −4 flip.im
  49. +59 −33 font.c → fontft1.c
  50. +3 −1 gaussian.im
  51. +5 −2 hlines.c
  52. +83 −46 image.c
  53. +61 −115 imager.h
  54. +26 −0 imageri.h
  55. +35 −0 imdatatypes.h
  56. +7 −9 imerror.h
  57. +52 −17 imext.c
  58. +43 −59 imext.h
  59. +34 −18 imexttypes.h
  60. +45 −21 img16.c
  61. +41 −61 img8.c
  62. +34 −17 imgdouble.c
  63. +88 −0 immacros.h
  64. +102 −50 iolayer.c
  65. +4 −4 iolayer.h
  66. +2 −0 iolayert.h
  67. +219 −13 lib/Imager/API.pod
  68. +310 −65 lib/Imager/APIRef.pod
  69. +76 −0 lib/Imager/Threads.pod
  70. +43 −38 limits.c
  71. +82 −42 log.c
  72. +19 −2 log.h
  73. +14 −4 maskimg.c
  74. +39 −0 mutexnull.c
  75. +44 −0 mutexpthr.c
  76. +94 −0 mutexwin.c
  77. +32 −13 palimg.c
  78. +8 −8 plug.h
  79. +36 −1 t/t82inline.t
  80. +92 −0 t/t84inlinectx.t
  81. +2 −0 t/x20spell.t
  82. +6 −2 t/x90cmpversion.t
  83. +2 −0 typemap.local
View
@@ -1,5 +1,20 @@
Imager release history. Older releases can be found in Changes.old
+ - improved thread safety
+ - the internal error stack and log file handle are now in a per-thread
+ context object
+ - JPEG now captures IPTC information in a thread-safe way
+ - avoid globals where possible for warning capture in libtiff
+ - use a mutex to avoid re-entering thread-unsafe giflib
+ - use a mutex to avoid re-entering thread-unsafe tifflib
+ - use a mutex to avoid re-entering thread-unsafe T1Lib
+ - use a library handle per thread for freetype 2.
+ - use an engine handle per thread for freetype 1.x.
+
+ - T1:
+ - improve error reporting
+ - provide better control of the level of anti-aliasing
+
Imager 0.92 - 14 Aug 2012
===========
View
@@ -1,6 +1,11 @@
Imager-Font-FT2 0.85
====================
+ - improve thread safety
+
+Imager-Font-FT2 0.85
+====================
+
- no longer fallback to using DynaLoader to load the XS code
https://rt.cpan.org/Ticket/Display.html?id=75560
View
@@ -1,11 +1,12 @@
package Imager::Font::FT2;
use strict;
use Imager;
+use Scalar::Util ();
use vars qw($VERSION @ISA);
@ISA = qw(Imager::Font);
BEGIN {
- $VERSION = "0.85";
+ $VERSION = "0.86";
require XSLoader;
XSLoader::load('Imager::Font::FT2', $VERSION);
@@ -50,6 +51,10 @@ sub new {
sub _draw {
my $self = shift;
+
+ $self->_valid
+ or return;
+
my %input = @_;
if (exists $input{channel}) {
i_ft2_cp($self->{id}, $input{image}{IMG}, $input{'x'}, $input{'y'},
@@ -69,12 +74,19 @@ sub _bounding_box {
my $self = shift;
my %input = @_;
+ $self->_valid
+ or return;
+
return i_ft2_bbox($self->{id}, $input{size}, $input{sizew}, $input{string},
$input{utf8});
}
sub dpi {
my $self = shift;
+
+ $self->_valid
+ or return;
+
my @old = i_ft2_getdpi($self->{id});
if (@_) {
my %hsh = @_;
@@ -97,12 +109,18 @@ sub dpi {
sub hinting {
my ($self, %opts) = @_;
+ $self->_valid
+ or return;
+
i_ft2_sethinting($self->{id}, $opts{hinting} || 0);
}
sub _transform {
my $self = shift;
+ $self->_valid
+ or return;
+
my %hsh = @_;
my $matrix = $hsh{matrix} or return undef;
@@ -117,6 +135,9 @@ sub utf8 {
sub has_chars {
my ($self, %hsh) = @_;
+ $self->_valid
+ or return;
+
unless (defined $hsh{string} && length $hsh{string}) {
$Imager::ERRSTR = "No string supplied to \$font->has_chars()";
return;
@@ -128,6 +149,9 @@ sub has_chars {
sub face_name {
my ($self) = @_;
+ $self->_valid
+ or return;
+
i_ft2_face_name($self->{id});
}
@@ -138,6 +162,9 @@ sub can_glyph_names {
sub glyph_names {
my ($self, %input) = @_;
+ $self->_valid
+ or return;
+
my $string = $input{string};
defined $string
or return Imager->_set_error("no string parameter passed to glyph_names");
@@ -154,12 +181,18 @@ sub glyph_names {
sub is_mm {
my ($self) = @_;
+ $self->_valid
+ or return;
+
i_ft2_is_multiple_master($self->{id});
}
sub mm_axes {
my ($self) = @_;
+ $self->_valid
+ or return;
+
my ($num_axis, $num_design, @axes) =
i_ft2_get_multiple_masters($self->{id})
or return Imager->_set_error(Imager->_error_as_msg);
@@ -170,6 +203,9 @@ sub mm_axes {
sub set_mm_coords {
my ($self, %opts) = @_;
+ $self->_valid
+ or return;
+
$opts{coords}
or return Imager->_set_error("Missing coords parameter");
ref($opts{coords}) && $opts{coords} =~ /ARRAY\(0x[\da-f]+\)$/
@@ -180,6 +216,19 @@ sub set_mm_coords {
return 1;
}
+
+# objects may be invalidated on thread creation (or Win32 fork emulation)
+sub _valid {
+ my $self = shift;
+
+ unless ($self->{id} && Scalar::Util::blessed($self->{id})) {
+ Imager->_set_error("font object was created in another thread");
+ return;
+ }
+
+ return 1;
+}
+
1;
__END__
View
@@ -356,3 +356,4 @@ i_ft2_set_mm_coords(handle, ...)
BOOT:
PERL_INITIALIZE_IMAGER_CALLBACKS;
+ i_ft2_start();
View
@@ -16,4 +16,5 @@ MANIFEST This list of files
MANIFEST.SKIP
README
t/t10ft2.t
+t/t20thread.t
typemap
View
@@ -61,6 +61,7 @@ else {
$opts{PREREQ_PM} =
{
@Imager_req,
+ 'Scalar::Util' => 1.00,
XSLoader => 0,
};
}
View
@@ -49,40 +49,84 @@ Truetype, Type1 and Windows FNT.
static void ft2_push_message(int code);
-static int ft2_initialized = 0;
-static FT_Library library;
+static void ft2_final(void *);
+
+static im_slot_t slot = -1;
+
+typedef struct {
+ int initialized;
+ FT_Library library;
+ im_context_t ctx;
+} ft2_state;
static i_img_dim i_min(i_img_dim a, i_img_dim b);
static i_img_dim i_max(i_img_dim a, i_img_dim b);
+void
+i_ft2_start(void) {
+ if (slot == -1)
+ slot = im_context_slot_new(ft2_final);
+}
+
/*
=item i_ft2_init(void)
Initializes the Freetype 2 library.
-Returns true on success, false on failure.
+Returns ft2_state * on success or NULL on failure.
=cut
*/
-int
+
+static ft2_state *
i_ft2_init(void) {
FT_Error error;
+ im_context_t ctx = im_get_context();
+ ft2_state *ft2 = im_context_slot_get(ctx, slot);
+
+ if (ft2 == NULL) {
+ ft2 = mymalloc(sizeof(ft2_state));
+ ft2->initialized = 0;
+ ft2->library = NULL;
+ ft2->ctx = ctx;
+ im_context_slot_set(ctx, slot, ft2);
+ mm_log((1, "created FT2 state %p for context %p\n", ft2, ctx));
+ }
i_clear_error();
- error = FT_Init_FreeType(&library);
- if (error) {
- ft2_push_message(error);
- i_push_error(0, "Initializing Freetype2");
- return 0;
+ if (!ft2->initialized) {
+ error = FT_Init_FreeType(&ft2->library);
+ if (error) {
+ ft2_push_message(error);
+ i_push_error(0, "Initializing Freetype2");
+ return NULL;
+ }
+ mm_log((1, "initialized FT2 state %p\n", ft2));
+
+ ft2->initialized = 1;
}
- ft2_initialized = 1;
+ return ft2;
+}
+
+static void
+ft2_final(void *state) {
+ ft2_state *ft2 = state;
+
+ if (ft2->initialized) {
+ mm_log((1, "finalizing FT2 state %p\n", state));
+ FT_Done_FreeType(ft2->library);
+ ft2->library = NULL;
+ ft2->initialized = 0;
+ }
- return 1;
+ mm_log((1, "freeing FT2 state %p\n", state));
+ myfree(state);
}
struct FT2_Fonthandle {
FT_Face face;
+ ft2_state *state;
int xdpi, ydpi;
int hint;
FT_Encoding encoding;
@@ -138,14 +182,15 @@ i_ft2_new(const char *name, int index) {
int i, j;
FT_Encoding encoding;
int score;
+ ft2_state *ft2;
mm_log((1, "i_ft2_new(name %p, index %d)\n", name, index));
- if (!ft2_initialized && !i_ft2_init())
+ if ((ft2 = i_ft2_init()) == NULL)
return NULL;
i_clear_error();
- error = FT_New_Face(library, name, index, &face);
+ error = FT_New_Face(ft2->library, name, index, &face);
if (error) {
ft2_push_message(error);
i_push_error(error, "Opening face");
@@ -173,6 +218,7 @@ i_ft2_new(const char *name, int index) {
result = mymalloc(sizeof(FT2_Fonthandle));
result->face = face;
+ result->state = ft2;
result->xdpi = result->ydpi = 72;
result->encoding = encoding;
@@ -244,7 +290,7 @@ i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi) {
if (xdpi > 0 && ydpi > 0) {
handle->xdpi = xdpi;
handle->ydpi = ydpi;
- return 0;
+ return 1;
}
else {
i_push_error(0, "resolutions must be positive");
View
@@ -7,7 +7,7 @@ typedef struct FT2_Fonthandle FT2_Fonthandle;
typedef FT2_Fonthandle* Imager__Font__FT2x;
-extern int i_ft2_init(void);
+extern void i_ft2_start(void);
extern FT2_Fonthandle * i_ft2_new(const char *name, int index);
extern void i_ft2_destroy(FT2_Fonthandle *handle);
extern int i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi);
View
@@ -0,0 +1,59 @@
+#!perl -w
+use strict;
+use Imager;
+
+use Config;
+my $loaded_threads;
+BEGIN {
+ if ($Config{useithreads} && $] > 5.008007) {
+ $loaded_threads =
+ eval {
+ require threads;
+ threads->import;
+ 1;
+ };
+ }
+}
+
+use Test::More;
+
+$Config{useithreads}
+ or plan skip_all => "can't test Imager's lack of threads support with no threads";
+$] > 5.008007
+ or plan skip_all => "require a perl with CLONE_SKIP to test Imager's lack of threads support";
+$loaded_threads
+ or plan skip_all => "couldn't load threads";
+
+$INC{"Devel/Cover.pm"}
+ and plan skip_all => "threads and Devel::Cover don't get along";
+
+# https://rt.cpan.org/Ticket/Display.html?id=65812
+# https://github.com/schwern/test-more/issues/labels/Test-Builder2#issue/100
+$Test::More::VERSION =~ /^2\.00_/
+ and plan skip_all => "threads are hosed in 2.00_06 and presumably all 2.00_*";
+
+plan tests => 8;
+
+Imager->open_log(log => "testout/t20thread.log");
+
+my $ft1 = Imager::Font->new(file => "fontfiles/dodge.ttf", type => "ft2");
+ok($ft1, "make a font");
+ok($ft1->_valid, "and it's valid");
+my $ft2;
+
+my $thr = threads->create
+ (
+ sub {
+ ok(!$ft1->_valid, "first font no longer valid");
+ $ft2 = Imager::Font->new(file => "fontfiles/dodge.ttf", type => "ft2");
+ ok($ft2, "make a new font in thread");
+ ok($ft2->_valid, "and it's valid");
+ 1;
+ },
+ );
+
+ok($thr->join, "join the thread");
+ok($ft1->_valid, "original font still valid in main thread");
+is($ft2, undef, "font created in thread shouldn't be set in main thread");
+
+Imager->close_log();
Oops, something went wrong. Retry.

0 comments on commit 02c6303

Please sign in to comment.