From 18e5facd9a6ceaac6f7597e86447237408dca5dd Mon Sep 17 00:00:00 2001
From: Lars Kanis <lars@greiz-reinsdorf.de>
Date: Thu, 19 Feb 2015 21:40:31 +0100
Subject: [PATCH] Release Ruby's GVL while calls to FXImage#savePixels and
 #loadPixels.

---
 ext/fox16_c/extconf.rb             |   2 +
 ext/fox16_c/gvl_wrappers.cpp       |  10 ++
 ext/fox16_c/include/FXRbCommon.h   |   3 +
 ext/fox16_c/include/FXRbImage.h    |   4 +-
 ext/fox16_c/include/gvl_wrappers.h | 180 +++++++++++++++++++++++++++++
 test/TC_FXJPGImage.rb              |  38 ++++++
 6 files changed, 235 insertions(+), 2 deletions(-)
 create mode 100644 ext/fox16_c/gvl_wrappers.cpp
 create mode 100644 ext/fox16_c/include/gvl_wrappers.h
 create mode 100644 test/TC_FXJPGImage.rb

diff --git a/ext/fox16_c/extconf.rb b/ext/fox16_c/extconf.rb
index 4263049..b397921 100755
--- a/ext/fox16_c/extconf.rb
+++ b/ext/fox16_c/extconf.rb
@@ -351,6 +351,7 @@ def do_rake_compiler_setup
   else
     FileUtils.move('scintilla_wrap.cpp', 'scintilla_wrap.cpp.bak') if FileTest.exist?('scintilla_wrap.cpp')
   end
+  have_func 'rb_thread_call_without_gvl'
 end
 
 # This directive processes the "--with-fox-include" and "--with-fox-lib"
@@ -383,4 +384,5 @@ $CFLAGS += " -DRUBY_1_9" if RUBY_VERSION =~ /1\.9\./
 $CFLAGS += " -DRUBY_2_0" if RUBY_VERSION =~ /2\.0\./
 
 # Last step: build the makefile
+create_header
 create_makefile("fox16_c")
diff --git a/ext/fox16_c/gvl_wrappers.cpp b/ext/fox16_c/gvl_wrappers.cpp
new file mode 100644
index 0000000..c55ee58
--- /dev/null
+++ b/ext/fox16_c/gvl_wrappers.cpp
@@ -0,0 +1,10 @@
+/*
+ * gvl_wrappers.c - Wrapper functions for locking/unlocking the Ruby GVL
+ *
+ */
+
+#include "FXRbCommon.h"
+
+FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_WRAPPER_STRUCT );
+FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_SKELETON );
+FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_STUB );
diff --git a/ext/fox16_c/include/FXRbCommon.h b/ext/fox16_c/include/FXRbCommon.h
index 2e76475..c353523 100644
--- a/ext/fox16_c/include/FXRbCommon.h
+++ b/ext/fox16_c/include/FXRbCommon.h
@@ -27,6 +27,8 @@
 extern "C" {
 #include "ruby.h"
 
+#include "extconf.h"
+
 #ifdef HAVE_RUBY_ENCODING_H
 #include "ruby/encoding.h"
 #endif
@@ -97,3 +99,4 @@ extern "C" {
 #include "FXScintilla.h"
 #endif
 #include "FXRuby.h"
+#include "gvl_wrappers.h"
diff --git a/ext/fox16_c/include/FXRbImage.h b/ext/fox16_c/include/FXRbImage.h
index c071d8d..6c6998c 100644
--- a/ext/fox16_c/include/FXRbImage.h
+++ b/ext/fox16_c/include/FXRbImage.h
@@ -73,10 +73,10 @@ inline void klass ## _gradient(klass* self,FXColor topleft,FXColor topright,FXCo
 inline void klass ## _blend(klass* self,FXColor color){ \
   self->klass::blend(color); \
   } \
-inline bool klass ## _savePixels(const klass* self,FXStream& store){ \
+inline bool klass ## _savePixels_gvl(const klass* self,FXStream& store){ \
   return self->klass::savePixels(store); \
   } \
-inline bool klass ## _loadPixels(klass* self,FXStream& store){ \
+inline bool klass ## _loadPixels_gvl(klass* self,FXStream& store){ \
   return self->klass::loadPixels(store); \
   }
 
diff --git a/ext/fox16_c/include/gvl_wrappers.h b/ext/fox16_c/include/gvl_wrappers.h
new file mode 100644
index 0000000..b4bd2f6
--- /dev/null
+++ b/ext/fox16_c/include/gvl_wrappers.h
@@ -0,0 +1,180 @@
+/*
+ * gvl_wrappers.h - Wrapper functions for locking/unlocking the Ruby GVL
+ *
+ * These are some obscure preprocessor directives that allow to generate
+ * drop-in replacement wrapper functions in a declarative manner.
+ * These wrapper functions ensure that ruby's GVL is released on each
+ * function call and reacquired at the end of the call or in callbacks.
+ * This way blocking functions calls don't block concurrent ruby threads.
+ *
+ * The wrapper of each function is prefixed by "gvl_".
+ *
+ * Use "gcc -E" to retrieve the generated code.
+ */
+
+#ifndef __gvl_wrappers_h
+#define __gvl_wrappers_h
+
+#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
+extern "C" void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1,
+         rb_unblock_function_t *ubf, void *data2);
+#endif
+
+#define DEFINE_PARAM_LIST1(type, name) \
+	name,
+
+#define DEFINE_PARAM_LIST2(type, name) \
+	p->params.name,
+
+#define DEFINE_PARAM_LIST3(type, name) \
+	type name,
+
+#define DEFINE_PARAM_DECL(type, name) \
+	type name;
+
+#define DEFINE_GVL_WRAPPER_STRUCT(name, when_non_void, rettype, lastparamtype, lastparamname) \
+	struct gvl_wrapper_##name##_params { \
+		struct { \
+			FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_DECL) \
+			lastparamtype lastparamname; \
+		} params; \
+		when_non_void( rettype retval; ) \
+	};
+
+#define DEFINE_GVL_SKELETON(name, when_non_void, rettype, lastparamtype, lastparamname) \
+	static void * gvl_##name##_skeleton( void *data ){ \
+		struct gvl_wrapper_##name##_params *p = (struct gvl_wrapper_##name##_params*)data; \
+		when_non_void( p->retval = ) \
+			name##_gvl( FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST2) p->params.lastparamname ); \
+		return NULL; \
+	}
+
+#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
+	#define DEFINE_GVL_STUB(name, when_non_void, rettype, lastparamtype, lastparamname) \
+		rettype name(FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST3) lastparamtype lastparamname){ \
+			struct gvl_wrapper_##name##_params params = { \
+				{FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST1) lastparamname}, when_non_void((rettype)0) \
+			}; \
+			rb_thread_call_without_gvl(gvl_##name##_skeleton, &params, RUBY_UBF_IO, 0); \
+			when_non_void( return params.retval; ) \
+		}
+#else
+	#define DEFINE_GVL_STUB(name, when_non_void, rettype, lastparamtype, lastparamname) \
+		rettype name(FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST3) lastparamtype lastparamname){ \
+			return name##_gvl( FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST1) lastparamname ); \
+		}
+#endif
+
+#define DEFINE_GVL_STUB_DECL(name, when_non_void, rettype, lastparamtype, lastparamname) \
+	rettype name(FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST3) lastparamtype lastparamname);
+
+#define DEFINE_GVL_TARGET_DECL(name, when_non_void, rettype, lastparamtype, lastparamname) \
+  rettype name##_gvl(FOR_EACH_PARAM_OF_##name(DEFINE_PARAM_LIST3) lastparamtype lastparamname);
+
+#define GVL_TYPE_VOID(string)
+#define GVL_TYPE_NONVOID(string) string
+
+
+/*
+ * Definitions of blocking functions and their parameters
+ */
+
+#define FOR_EACH_PARAM_OF_FXImage_loadPixels(param) \
+  param(FXImage *, self)
+#define FOR_EACH_PARAM_OF_FXImage_savePixels(param) \
+  param(const FXImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXBMPImage_loadPixels(param) \
+  param(FXBMPImage *, self)
+#define FOR_EACH_PARAM_OF_FXBMPImage_savePixels(param) \
+  param(const FXBMPImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXJPGImage_loadPixels(param) \
+  param(FXJPGImage *, self)
+#define FOR_EACH_PARAM_OF_FXJPGImage_savePixels(param) \
+  param(const FXJPGImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXGIFImage_loadPixels(param) \
+  param(FXGIFImage *, self)
+#define FOR_EACH_PARAM_OF_FXGIFImage_savePixels(param) \
+  param(const FXGIFImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXICOImage_loadPixels(param) \
+  param(FXICOImage *, self)
+#define FOR_EACH_PARAM_OF_FXICOImage_savePixels(param) \
+  param(const FXICOImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXPNGImage_loadPixels(param) \
+  param(FXPNGImage *, self)
+#define FOR_EACH_PARAM_OF_FXPNGImage_savePixels(param) \
+  param(const FXPNGImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXPPMImage_loadPixels(param) \
+  param(FXPPMImage *, self)
+#define FOR_EACH_PARAM_OF_FXPPMImage_savePixels(param) \
+  param(const FXPPMImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXPCXImage_loadPixels(param) \
+  param(FXPCXImage *, self)
+#define FOR_EACH_PARAM_OF_FXPCXImage_savePixels(param) \
+  param(const FXPCXImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXRGBImage_loadPixels(param) \
+  param(FXRGBImage *, self)
+#define FOR_EACH_PARAM_OF_FXRGBImage_savePixels(param) \
+  param(const FXRGBImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXTGAImage_loadPixels(param) \
+  param(FXTGAImage *, self)
+#define FOR_EACH_PARAM_OF_FXTGAImage_savePixels(param) \
+  param(const FXTGAImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXTIFImage_loadPixels(param) \
+  param(FXTIFImage *, self)
+#define FOR_EACH_PARAM_OF_FXTIFImage_savePixels(param) \
+  param(const FXTIFImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXXBMImage_loadPixels(param) \
+  param(FXXBMImage *, self)
+#define FOR_EACH_PARAM_OF_FXXBMImage_savePixels(param) \
+  param(const FXXBMImage *, self)
+
+#define FOR_EACH_PARAM_OF_FXXPMImage_loadPixels(param) \
+  param(FXXPMImage *, self)
+#define FOR_EACH_PARAM_OF_FXXPMImage_savePixels(param) \
+  param(const FXXPMImage *, self)
+
+/* function( name, void_or_nonvoid, returntype, lastparamtype, lastparamname ) */
+#define FOR_EACH_BLOCKING_FUNCTION(function) \
+  function(FXImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXBMPImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXBMPImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXJPGImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXJPGImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXGIFImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXGIFImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXICOImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXICOImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPNGImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPNGImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPPMImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPPMImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPCXImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXPCXImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXRGBImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXRGBImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXTGAImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXTGAImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXTIFImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXTIFImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXXBMImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXXBMImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXXPMImage_loadPixels, GVL_TYPE_NONVOID, bool, FXStream&, store) \
+  function(FXXPMImage_savePixels, GVL_TYPE_NONVOID, bool, FXStream&, store)
+
+
+FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_STUB_DECL )
+FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_TARGET_DECL )
+
+#endif /* end __gvl_wrappers_h */
diff --git a/test/TC_FXJPGImage.rb b/test/TC_FXJPGImage.rb
new file mode 100644
index 0000000..ea016e6
--- /dev/null
+++ b/test/TC_FXJPGImage.rb
@@ -0,0 +1,38 @@
+require 'fox16'
+require 'test/unit'
+require 'testcase'
+require 'openssl'
+
+class TC_FXJPGImage < Fox::TestCase
+  include Fox
+
+  def setup
+    super(self.class.name)
+  end
+
+  def test_save_with_thread
+    w, h = 4000, 3000
+    img_data = OpenSSL::Random.random_bytes(w) * h * 4
+
+    count = 0
+    th = Thread.new do
+      loop do
+        sleep 0.01
+        count += 1
+      end
+    end
+
+    img = FXJPGImage.new(app)
+    img.setPixels( img_data, 0, w, h )
+
+    jpeg_data = FXMemoryStream.open(FXStreamSave, nil) do |outfile|
+    img.savePixels(outfile)
+      outfile.takeBuffer
+    end
+
+    th.kill
+
+    assert_operator(count, :>=, 10)
+    assert_operator(jpeg_data.bytesize, :>=, 1000)
+  end
+end
-- 
GitLab