Bug #703 [ruby-core:19668]
string output duplication occurs if the same file descriptor written to in different threads
| Status : | Closed | Start : | 11/01/2008 | |
| Priority : | Normal | Due date : | ||
| Assigned to : | - | % Done : | 100% |
|
| Category : | - | |||
| Target version : | - | |||
| ruby -v : |
Description
ruby-dev:32566
a = Thread.new { p '4'}
b = Thread.new { p '3' }
a.join
b.join
results in
"4"
"3"
"4"
"3"
because the buffer is modified simultaneously by two threads within io_fflush [I think].
Associated revisions
- include/ruby/io.h (rb_io_t): added write_lock to serialize.
[ruby-core:19668] - gc.c (gc_mark_children): mark write_lock.
- gc.c (rb_gc_call_finalizer_at_exit): Mutex also needs to be deferred.
- io.c (io_fflush, io_binwrite, fptr_finalize): serializes writing.
History
11/03/2008 02:34 AM - hemant kumar
I do not see this problem, with: ruby 1.9.0 (2008-09-30 revision 0) [i686-linux]
11/03/2008 09:06 AM - Yukihiro Matsumoto
Hi, In message "Re:[ruby-core:19668][Bug#703] string output duplication occurs if the same file descriptor written to in different threads" on Sat, 1 Nov 2008 02:38:52 +0900, Roger Pack <redmine@ruby-lang.org> writes: |ruby-dev:32566 It's not related to ruby-dev:32566, I think. |a = Thread.new { p '4'} |b = Thread.new { p '3' } |a.join |b.join | |results in |"4" |"3" |"4" |"3" | |because the buffer is modified simultaneously by two threads within io_fflush [I think]. It doesn't happen on either trunk nor 1.8.7. What's your version, and platform? matz.
11/03/2008 09:46 AM - Nobuyoshi Nakada
Hi, At Mon, 3 Nov 2008 09:04:37 +0900, Yukihiro Matsumoto wrote in [ruby-core:19676]: > It doesn't happen on either trunk nor 1.8.7. What's your version, and > platform? It could reproduced with full ruby, but not with miniruby nor full ruby with disabling gems. $ ./ruby -v bug-703.rb ruby 1.9.0 (2008-11-03 revision 20092) [i686-linux] "4" "3" "4" "3" $ ./ruby --disable-gems bug-703.rb "4" "3" Interestingly, -v makes it occur again. $ ./ruby -v --disable-gems bug-703.rb ruby 1.9.0 (2008-11-03 revision 20092) [i686-linux] "4" "3" "4" "3" But -v after --disable-gems doesn't. $ ./ruby --disable-gems -v bug-703.rb ruby 1.9.0 (2008-11-03 revision 20092) [i686-linux] "4" "3" -- Nobu Nakada
11/04/2008 02:46 AM - Nobuyoshi Nakada
Hi,
At Mon, 3 Nov 2008 09:44:44 +0900,
Nobuyoshi Nakada wrote in [ruby-core:19678]:
> > It doesn't happen on either trunk nor 1.8.7. What's your version, and
> > platform?
>
> It could reproduced with full ruby, but not with miniruby nor
> full ruby with disabling gems.
It seems a mere timing and probability issue. And seems solved
by serializing write operations, as Roger figured out.
Index: gc.c
===================================================================
--- gc.c (revision 20101)
+++ gc.c (working copy)
@@ -1509,4 +1509,5 @@ gc_mark_children(rb_objspace_t *objspace
gc_mark(objspace, obj->as.file.fptr->writeconv_pre_ecopts, lev);
gc_mark(objspace, obj->as.file.fptr->encs.ecopts, lev);
+ gc_mark(objspace, obj->as.file.fptr->write_lock, lev);
}
break;
Index: io.c
===================================================================
--- io.c (revision 20101)
+++ io.c (working copy)
@@ -525,9 +525,27 @@ rb_write_internal(int fd, void *buf, siz
}
+static long
+io_writable_length(rb_io_t *fptr, long l)
+{
+ if (PIPE_BUF < l &&
+ !rb_thread_alone() &&
+ wsplit_p(fptr)) {
+ l = PIPE_BUF;
+ }
+ return l;
+}
+
+static VALUE
+io_flush_buffer(VALUE arg)
+{
+ rb_io_t *fptr = (rb_io_t *)arg;
+ long l = io_writable_length(fptr, fptr->wbuf_len);
+ return rb_write_internal(fptr->fd, fptr->wbuf+fptr->wbuf_off, l);
+}
+
static int
io_fflush(rb_io_t *fptr)
{
- int r, l;
- int wbuf_off, wbuf_len;
+ long r;
rb_io_check_closed(fptr);
@@ -540,13 +558,5 @@ io_fflush(rb_io_t *fptr)
if (fptr->wbuf_len == 0)
return 0;
- wbuf_off = fptr->wbuf_off;
- wbuf_len = fptr->wbuf_len;
- l = wbuf_len;
- if (PIPE_BUF < l &&
- !rb_thread_alone() &&
- wsplit_p(fptr)) {
- l = PIPE_BUF;
- }
- r = rb_write_internal(fptr->fd, fptr->wbuf+wbuf_off, l);
+ r = rb_mutex_synchronize(fptr->write_lock, io_flush_buffer, (VALUE)fptr);
/* xxx: Other threads may modify wbuf.
* A lock is required, definitely. */
@@ -732,9 +742,23 @@ make_writeconv(rb_io_t *fptr)
/* writing functions */
+struct binwrite_arg {
+ rb_io_t *fptr;
+ VALUE str;
+ long offset;
+ long length;
+};
+
+static VALUE
+io_binwrite_string(VALUE arg)
+{
+ struct binwrite_arg *p = (struct binwrite_arg *)arg;
+ long l = io_writable_length(p->fptr, p->length);
+ return rb_write_internal(p->fptr->fd, RSTRING_PTR(p->str)+p->offset, l);
+}
static long
io_binwrite(VALUE str, rb_io_t *fptr, int nosync)
{
- long len, n, r, l, offset = 0;
+ long len, n, r, offset = 0;
len = RSTRING_LEN(str);
@@ -745,7 +769,10 @@ io_binwrite(VALUE str, rb_io_t *fptr, in
fptr->wbuf_capa = 8192;
fptr->wbuf = ALLOC_N(char, fptr->wbuf_capa);
+ fptr->write_lock = rb_mutex_new();
}
if ((!nosync && (fptr->mode & (FMODE_SYNC|FMODE_TTY))) ||
(fptr->wbuf && fptr->wbuf_capa <= fptr->wbuf_len + len)) {
+ struct binwrite_arg arg;
+
/* xxx: use writev to avoid double write if available */
if (fptr->wbuf_len && fptr->wbuf_len+len <= fptr->wbuf_capa) {
@@ -767,12 +794,10 @@ io_binwrite(VALUE str, rb_io_t *fptr, in
rb_io_check_closed(fptr);
}
+ arg.fptr = fptr;
+ arg.str = str;
+ arg.offset = offset;
retry:
- l = n;
- if (PIPE_BUF < l &&
- !rb_thread_alone() &&
- wsplit_p(fptr)) {
- l = PIPE_BUF;
- }
- r = rb_write_internal(fptr->fd, RSTRING_PTR(str)+offset, l);
+ arg.length = n;
+ r = rb_mutex_synchronize(fptr->write_lock, io_binwrite_string, (VALUE)&arg);
/* xxx: other threads may modify given string. */
if (r == n) return len;
@@ -3040,4 +3065,17 @@ finish_writeconv(rb_io_t *fptr, int nora
}
+struct finish_writeconv_arg {
+ rb_io_t *fptr;
+ int noraise;
+};
+
+static VALUE
+finish_writeconv_sync(VALUE arg)
+{
+ struct finish_writeconv_arg *p = (struct finish_writeconv_arg *)arg;
+ finish_writeconv(p->fptr, p->noraise);
+ return Qnil;
+}
+
static void
fptr_finalize(rb_io_t *fptr, int noraise)
@@ -3045,5 +3083,13 @@ fptr_finalize(rb_io_t *fptr, int noraise
int ebadf = 0;
if (fptr->writeconv) {
- finish_writeconv(fptr, noraise);
+ if (fptr->write_lock) {
+ struct finish_writeconv_arg arg;
+ arg.fptr = fptr;
+ arg.noraise = noraise;
+ rb_mutex_synchronize(fptr->write_lock, finish_writeconv_sync, (VALUE)&arg);
+ }
+ else {
+ finish_writeconv(fptr, noraise);
+ }
}
if (fptr->wbuf_len) {
Index: include/ruby/io.h
===================================================================
--- include/ruby/io.h (revision 20101)
+++ include/ruby/io.h (working copy)
@@ -74,4 +74,5 @@ typedef struct rb_io_t {
int writeconv_initialized;
+ VALUE write_lock;
} rb_io_t;
@@ -134,4 +135,5 @@ typedef struct rb_io_t {
fp->encs.ecflags = 0;\
fp->encs.ecopts = Qnil;\
+ fp->write_lock = 0;\
} while (0)
--
Nobu Nakada
11/08/2008 05:47 AM - Nobuyoshi Nakada
- Status changed from Open to Closed
- % Done changed from 0 to 100
Applied in changeset r20144.