[ruby-oci8-commit] [449] trunk/ruby-oci8: clear an executuing thread information in a connection when a SQL

nobody at rubyforge.org nobody at rubyforge.org
Fri Sep 30 08:18:25 EDT 2011


Revision: 449
Author:   kubo
Date:     2011-09-30 08:18:23 -0400 (Fri, 30 Sep 2011)

Log Message:
-----------
clear an executuing thread information in a connection when a SQL
executions is canceled by Thread#kill or Timeout::timeout.
(reported by Aaron Qian)
See: http://rubyforge.org/forum/forum.php?thread_id=50112&forum_id=1078

Modified Paths:
--------------
    trunk/ruby-oci8/ChangeLog
    trunk/ruby-oci8/ext/oci8/apiwrap.c.tmpl
    trunk/ruby-oci8/ext/oci8/oci8lib.c
    trunk/ruby-oci8/test/test_break.rb

Modified: trunk/ruby-oci8/ChangeLog
===================================================================
--- trunk/ruby-oci8/ChangeLog	2011-09-30 12:11:16 UTC (rev 448)
+++ trunk/ruby-oci8/ChangeLog	2011-09-30 12:18:23 UTC (rev 449)
@@ -1,4 +1,11 @@
 2011-09-30  KUBO Takehiro  <kubo at jiubao.org>
+	* ext/oci8/apiwrap.c.tmpl, ext/oci8/oci8lib.c, test/test_break.rb:
+	    clear an executuing thread information in a connection when a SQL
+	    executions is canceled by Thread#kill or Timeout::timeout.
+	    (reported by Aaron Qian)
+	    See: http://rubyforge.org/forum/forum.php?thread_id=50112&forum_id=1078
+
+2011-09-30  KUBO Takehiro  <kubo at jiubao.org>
 	* ext/oci8/connection_pool.c: run connection-pool cleanup functions
 	    in a native thread not to block GC.
 	* ext/oci8/oci8.c: use xfree() instead of free() to release memory

Modified: trunk/ruby-oci8/ext/oci8/apiwrap.c.tmpl
===================================================================
--- trunk/ruby-oci8/ext/oci8/apiwrap.c.tmpl	2011-09-30 12:11:16 UTC (rev 448)
+++ trunk/ruby-oci8/ext/oci8/apiwrap.c.tmpl	2011-09-30 12:18:23 UTC (rev 449)
@@ -4,6 +4,12 @@
 %>
 #define API_WRAP_C 1
 #include "apiwrap.h"
+#ifdef HAVE_RB_THREAD_BLOCKING_REGION
+#define BLOCKING_FUNCTION_EPILOGUE(svcctx) do { (svcctx)->executing_thread = Qnil; } while (0)
+#else
+#define BLOCKING_FUNCTION_EPILOGUE(svcctx) do { } while (0)
+#endif
+
 <%
 prev_name = ''
 funcs.each do |f|
@@ -33,6 +39,7 @@
  * <%=f.name%>_nb
  */
 typedef struct {
+    oci8_svcctx_t *svcctx;
 <%
    f.ret != 'void'
 %>    <%= f.ret %> rv;
@@ -56,9 +63,11 @@
 %>    data->rv = <%=f.name%>(<%= f.args.collect do |a| 'data->' + a.name; end.join(', ') %>);
 <% end %>
 <% if f.ret == 'sword'
-%>    return (VALUE)data->rv;
+%>    BLOCKING_FUNCTION_EPILOGUE(data->svcctx);
+    return (VALUE)data->rv;
 <% else
-%>    return (VALUE)0;
+%>    BLOCKING_FUNCTION_EPILOGUE(data->svcctx);
+    return (VALUE)0;
 <% end %>
 }
 #else
@@ -69,6 +78,7 @@
 {
     if (have_<%=f.name%>_nb) {
         oci8_<%=f.name%>_data_t data;
+        data.svcctx = svcctx;
 <% f.args.each do |a|
 %>        data.<%=a.name%> = <%=a.name%>;
 <% end

Modified: trunk/ruby-oci8/ext/oci8/oci8lib.c
===================================================================
--- trunk/ruby-oci8/ext/oci8/oci8lib.c	2011-09-30 12:11:16 UTC (rev 448)
+++ trunk/ruby-oci8/ext/oci8/oci8lib.c	2011-09-30 12:18:23 UTC (rev 449)
@@ -246,8 +246,8 @@
             rb_raise(rb_eRuntimeError /* FIXME */, "executing in another thread");
         }
         svcctx->executing_thread = rb_thread_current();
+        /* Note: executing_thread is cleard at the end of the blocking function. */
         rv = (sword)rb_thread_blocking_region(func, data, oci8_unblock_func, svcctx);
-        svcctx->executing_thread = Qnil;
         if (rv == OCI_ERROR) {
             if (oci8_get_error_code(oci8_errhp) == 1013) {
                 rb_raise(eOCIBreak, "Canceled by user request.");
@@ -261,14 +261,20 @@
 #else /* HAVE_RB_THREAD_BLOCKING_REGION */
 
 /* ruby 1.8 */
-sword oci8_blocking_region(oci8_svcctx_t *svcctx, rb_blocking_function_t func, void *data)
+typedef struct {
+    oci8_svcctx_t *svcctx;
+    rb_blocking_function_t *func;
+    void *data;
+} blocking_region_arg_t;
+
+static VALUE blocking_function_execute(blocking_region_arg_t *arg)
 {
+    oci8_svcctx_t *svcctx = arg->svcctx;
+    rb_blocking_function_t *func = arg->func;
+    void *data = arg->data;
     struct timeval tv;
     sword rv;
 
-    if (!NIL_P(svcctx->executing_thread)) {
-        rb_raise(rb_eRuntimeError /* FIXME */, "executing in another thread");
-    }
     tv.tv_sec = 0;
     tv.tv_usec = 10000;
     svcctx->executing_thread = rb_thread_current();
@@ -279,8 +285,7 @@
     }
     if (rv == OCI_ERROR) {
         if (oci8_get_error_code(oci8_errhp) == 1013) {
-            if (have_OCIReset)
-                OCIReset(svcctx->base.hp.ptr, oci8_errhp);
+            OCIReset(svcctx->base.hp.ptr, oci8_errhp);
             svcctx->executing_thread = Qnil;
             rb_raise(eOCIBreak, "Canceled by user request.");
         }
@@ -288,6 +293,30 @@
     svcctx->executing_thread = Qnil;
     return rv;
 }
+
+static VALUE blocking_function_ensure(oci8_svcctx_t *svcctx)
+{
+    if (!NIL_P(svcctx->executing_thread)) {
+        /* The thread is killed. */
+        OCIBreak(svcctx->base.hp.ptr, oci8_errhp);
+        OCIReset(svcctx->base.hp.ptr, oci8_errhp);
+        svcctx->executing_thread = Qnil;
+    }
+    return Qnil;
+}
+
+sword oci8_blocking_region(oci8_svcctx_t *svcctx, rb_blocking_function_t func, void *data)
+{
+    blocking_region_arg_t arg;
+
+    arg.svcctx = svcctx;
+    arg.func = func;
+    arg.data = data;
+    if (!NIL_P(svcctx->executing_thread)) {
+        rb_raise(rb_eRuntimeError, "executing in another thread");
+    }
+    return (sword)rb_ensure(blocking_function_execute, (VALUE)&arg, blocking_function_ensure, (VALUE)svcctx);
+}
 #endif /* HAVE_RB_THREAD_BLOCKING_REGION */
 
 typedef struct {

Modified: trunk/ruby-oci8/test/test_break.rb
===================================================================
--- trunk/ruby-oci8/test/test_break.rb	2011-09-30 12:11:16 UTC (rev 448)
+++ trunk/ruby-oci8/test/test_break.rb	2011-09-30 12:18:23 UTC (rev 449)
@@ -1,6 +1,7 @@
 # High-level API
 require 'oci8'
 require 'test/unit'
+require 'timeout'
 require File.dirname(__FILE__) + '/config'
 
 class TestBreak < Test::Unit::TestCase
@@ -79,4 +80,16 @@
     expect[SEND_BREAK]   = TIME_TO_BREAK
     do_test_ocibreak(@conn, expect)
   end
+
+  def test_timeout
+    @conn.non_blocking = true
+    start_time = Time.now
+    assert_raise(Timeout::Error) do
+      Timeout.timeout(1) do
+        @conn.exec("BEGIN DBMS_LOCK.SLEEP(10); END;")
+      end
+    end
+    @conn.exec("BEGIN NULL; END;")
+    assert_operator(Time.now, :<, start_time + 2)
+  end
 end




More information about the ruby-oci8-commit mailing list