[Bug:1.9] context switch may occur during freeing io

e$B1sF#$G$9!#e(B

e$B0J2<$N$h$&$Ke(B do_select e$B$Ne(B blocking region e$B$Ke(B
native_thread_yield e$B$re(B
e$BF~$l$?>e$G!"e(B

Index: thread.c

— thread.c (revision 18124)
+++ thread.c (working copy)
@@ -2004,6 +2004,7 @@
}
#else
BLOCKING_REGION({

  • native_thread_yield();
    result = select(n, read, write, except, timeout);
    if (result < 0) lerrno = errno;
    }, ubf_select, GET_THREAD());

e$B0J2<$r<B9T$9$k$H!"$?$^$KMn$A$^$9!#e(B

$ ./ruby
t = Thread.new do
loop do
w = IO.pipe.last
w.sync = false
w.write(“a” * 1000)
end
end
sleep 0.1
Thread.new { }
GC.start

-:3: [BUG] object allocation during garbage collection phase
ruby 1.9.0 (2008-07-18 revision 18124) [i686-linux]

– control frame ----------
c:0008 p:---- s:0015 b:0015 l:000014 d:000014 CFUNC :(null)
c:0007 p:---- s:0013 b:0013 l:000012 d:000012 CFUNC :pipe
c:0006 p:0013 s:0010 b:0010 l:000f88 d:000009 BLOCK -:3
c:0005 p:---- s:0009 b:0009 l:000008 d:000008 FINISH
c:0004 p:---- s:0007 b:0007 l:000006 d:000006 CFUNC :loop
c:0003 p:0007 s:0004 b:0004 l:000f88 d:000003 BLOCK -:2
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH
c:0001 p:---- s:0002 b:0002 l:000001 d:000001 TOP

DBG> : “-:3:in (null)'" DBG> : "-:3:inpipe’”
DBG> : “-:3:in block (2 levels) in <main>'" DBG> : "-:2:inloop’”
DBG> : “-:2:in `block in '”
– backtrace of native function call (Use addr2line) –
0x8102065
0x812948e
0x81294eb
0x8061b3a
0x806953c
0x80fdc90
0x80fe0f2
0x807e180
0x807e982
0x8066852
0x8059e20
0x806a768
0x80f2efe
0x80f4f4c
0x80f7eb9
0x80fbdcb
0x80fc414
0x80fc962
0x8059fec
0x80f4063
0x80f4f4c
0x80f7eb9
0x80fbdcb
0x80fc414
0x80fc784
0x8106b9c
0x8106be1
0xb7f6a240
0xb7e9e49e

e$B%"%!<%H$7$^$7$?e(B

e$B860x$O!"e(BT_FILE e$B$,e(B GC e$B$5$l$k$H$-e(B

fptr_finalize
-> io_fflush
-> rb_thread_fd_writable
-> rb_thread_wait_fd_rw
-> do_select
-> BLOCKING_REGION

e$B$H8F$S=P$7$,?J$s$G!"e(BGC
e$BCf$KB>$N%9%l%C%I$,F0$-=P$7$F$7$^$&$?$a$G$9!#e(B

e$B$3$NLdBj$,$J$+$C$?$H$7$F$b!“e(Bfptr_finalize e$B$Ge(B io_fflush
e$B$9$k$N$Oe(B
e$B%V%m%C%/$9$k2DG=@-$,$”$C$F$^$:$$5$$,$7$^$9!#$I$&$7$?$b$s$G$7$g$&!#e(B

e$B$J$+$@$G$9!#e(B

At Fri, 18 Jul 2008 19:47:28 +0900,
Yusuke ENDOH wrote in [ruby-dev:35578]:

e$B860x$O!"e(BT_FILE e$B$,e(B GC e$B$5$l$k$H$-e(B

fptr_finalize
-> io_fflush
-> rb_thread_fd_writable
-> rb_thread_wait_fd_rw
-> do_select
-> BLOCKING_REGION

e$B$H8F$S=P$7$,?J$s$G!"e(BGC e$BCf$KB>$N%9%l%C%I$,F0$-=P$7$F$7$^$&$?$a$G$9!#e(B

T_DATAe$B$Ne(Bdfreee$B$He(Bfptr_finalizee$B$Oe(Bdeferrede$B$K2s$7$^$7$g$&$+!#e(B

e$B$3$NLdBj$,$J$+$C$?$H$7$F$b!“e(Bfptr_finalize e$B$Ge(B io_fflush e$B$9$k$N$Oe(B
e$B%V%m%C%/$9$k2DG=@-$,$”$C$F$^$:$$5$$,$7$^$9!#$I$&$7$?$b$s$G$7$g$&!#e(B

e$B$3$C$A$O$I$&$7$^$7$g$&$+$M$’!#e(B

Index: gc.c

— gc.c (revision 18132)
+++ gc.c (working copy)
@@ -1324,5 +1324,5 @@ gc_mark_children(rb_objspace_t *objspace
}

-static void obj_free(rb_objspace_t *, VALUE);
+static int obj_free(rb_objspace_t *, VALUE);

static void
@@ -1402,9 +1402,7 @@ gc_sweep(rb_objspace_t *objspace)
while (p < pend) {
if (!(p->as.basic.flags & FL_MARK)) {

  • if (p->as.basic.flags) {
  •    obj_free(objspace, (VALUE)p);
    
  • }
  • if (need_call_final && FL_TEST(p, FL_FINALIZE)) {
  •    p->as.free.flags = FL_MARK; /* remain marked */
    
  • if (p->as.basic.flags && obj_free(objspace, (VALUE)p) ||
  •    need_call_final && FL_TEST(p, FL_FINALIZE)) {
    
  •    p->as.free.flags |= FL_MARK; /* remain marked */
       p->as.free.next = final_list;
       final_list = p;
    

@@ -1470,5 +1468,5 @@ rb_gc_force_recycle(VALUE p)
}

-static void
+static int
obj_free(rb_objspace_t *objspace, VALUE obj)
{
@@ -1527,5 +1525,5 @@ obj_free(rb_objspace_t *objspace, VALUE
}
else if (RANY(obj)->as.data.dfree) {

  • (*RANY(obj)->as.data.dfree)(DATA_PTR(obj));
  • return 1;
    }
    }
    @@ -1542,5 +1540,5 @@ obj_free(rb_objspace_t *objspace, VALUE
    case T_FILE:
    if (RANY(obj)->as.file.fptr) {
  •  rb_io_fptr_finalize(RANY(obj)->as.file.fptr);
    
  •  return 1;
    
    }
    break;
    @@ -1571,5 +1569,5 @@ obj_free(rb_objspace_t *objspace, VALUE
    break;
    }
  • return; /* no need to free iv_tbl */
  • break; /* no need to free iv_tbl */

    case T_STRUCT:
    

@@ -1584,4 +1582,6 @@ obj_free(rb_objspace_t objspace, VALUE
RANY(obj)->as.basic.flags & T_MASK, (void
)obj);
}

  • RANY(obj)->as.basic.flags &= ~T_MASK;
  • return 0;
    }

@@ -2010,4 +2010,12 @@ run_final(rb_objspace_t objspace, VALUE
objid = rb_obj_id(obj); /
make obj into id */
rb_thread_critical = Qtrue;

  • switch (RANY(obj)->as.basic.flags & T_MASK) {
  •  case T_DATA:
    
  • (*RANY(obj)->as.data.dfree)(DATA_PTR(obj));
  • break;
  •  case T_FILE:
    
  • rb_io_fptr_finalize(RANY(obj)->as.file.fptr);
  • break;
  • }
    args[1] = 0;
    args[2] = (VALUE)rb_safe_level();

e$B$J$+$@$G$9!#e(B

At Sun, 20 Jul 2008 08:27:35 +0900,
Nobuyoshi N. wrote in [ruby-dev:35594]:

e$B$H8F$S=P$7$,?J$s$G!"e(BGC e$BCf$KB>$N%9%l%C%I$,F0$-=P$7$F$7$^$&$?$a$G$9!#e(B

T_DATAe$B$Ne(Bdfreee$B$He(Bfptr_finalizee$B$Oe(Bdeferrede$B$K2s$7$^$7$g$&$+!#e(B

ObjectSpacee$B$+$i8+$($F$7$^$&$H$^$:$$$N$G!"D{@5$G$9!#e(B

Index: gc.c

— gc.c (revision 18139)
+++ gc.c (working copy)
@@ -1317,4 +1317,7 @@ gc_mark_children(rb_objspace_t *objspace
break;

  •  case T_UNDEF:
    
  • break;
  •  default:
    
    rb_bug(“rb_gc_mark(): unknown data type 0x%lx(%p) %s”,
    @@ -1324,5 +1327,5 @@ gc_mark_children(rb_objspace_t *objspace
    }

-static void obj_free(rb_objspace_t *, VALUE);
+static int obj_free(rb_objspace_t *, VALUE);

static void
@@ -1402,9 +1405,7 @@ gc_sweep(rb_objspace_t *objspace)
while (p < pend) {
if (!(p->as.basic.flags & FL_MARK)) {

  • if (p->as.basic.flags) {
  •    obj_free(objspace, (VALUE)p);
    
  • }
  • if (need_call_final && FL_TEST(p, FL_FINALIZE)) {
  •    p->as.free.flags = FL_MARK; /* remain marked */
    
  • if (p->as.basic.flags && obj_free(objspace, (VALUE)p) ||
  •    need_call_final && FL_TEST(p, FL_FINALIZE)) {
    
  •    p->as.free.flags |= FL_MARK; /* remain marked */
       p->as.free.next = final_list;
       final_list = p;
    

@@ -1470,5 +1471,5 @@ rb_gc_force_recycle(VALUE p)
}

-static void
+static int
obj_free(rb_objspace_t *objspace, VALUE obj)
{
@@ -1527,5 +1528,7 @@ obj_free(rb_objspace_t *objspace, VALUE
}
else if (RANY(obj)->as.data.dfree) {

  • (*RANY(obj)->as.data.dfree)(DATA_PTR(obj));
  • RANY(obj)->as.basic.flags &= ~T_MASK;
  • RANY(obj)->as.basic.flags |= T_UNDEF;
  • return 1;
    }
    }
    @@ -1542,5 +1545,10 @@ obj_free(rb_objspace_t *objspace, VALUE
    case T_FILE:
    if (RANY(obj)->as.file.fptr) {
  •  rb_io_fptr_finalize(RANY(obj)->as.file.fptr);
    
  •  rb_io_t *fptr = RANY(obj)->as.file.fptr;
    
  •  RANY(obj)->as.basic.flags &= ~T_MASK;
    
  •  RANY(obj)->as.basic.flags |= T_UNDEF;
    
  •  RDATA(obj)->dfree = (void (*)(void*))rb_io_fptr_finalize;
    
  •  RDATA(obj)->data = fptr;
    
  •  return 1;
    
    }
    break;
    @@ -1571,5 +1579,5 @@ obj_free(rb_objspace_t *objspace, VALUE
    break;
    }
  • return; /* no need to free iv_tbl */
  • break; /* no need to free iv_tbl */

    case T_STRUCT:
    

@@ -1580,8 +1588,13 @@ obj_free(rb_objspace_t *objspace, VALUE
break;

  •  case T_UNDEF:
    
  • break;
  •  default:
    
    rb_bug(“gc_sweep(): unknown data type 0x%lx(%p)”,
    RANY(obj)->as.basic.flags & T_MASK, (void*)obj);
    }
  • RANY(obj)->as.basic.flags &= ~T_MASK;
  • return 0;
    }

@@ -1855,4 +1868,5 @@ os_obj_of(rb_objspace_t *objspace, VALUE
case T_ICLASS:
case T_NODE:

  •  case T_UNDEF:
       continue;
     case T_CLASS:
    

@@ -2010,4 +2024,7 @@ run_final(rb_objspace_t objspace, VALUE
objid = rb_obj_id(obj); /
make obj into id */
rb_thread_critical = Qtrue;

  • if ((RANY(obj)->as.basic.flags & T_MASK) == T_UNDEF) {
  • (*RANY(obj)->as.data.dfree)(DATA_PTR(obj));
  • }
    args[1] = 0;
    args[2] = (VALUE)rb_safe_level();

e$B%A%1%C%He(B #293 e$B$,99?7$5$l$^$7$?!#e(B (by Nobuyoshi N.)

e$B%9%F!<%?%9e(B Opene$B$+$ie(BClosede$B$KJQ99e(B
e$B?JD=e(B % 0e$B$+$ie(B100e$B$KJQ99e(B

Applied in changeset r18221.

http://redmine.ruby-lang.org/issues/show/293