From 635d0822cdb3a9eb7612ea478601d17ebe76b74d Mon Sep 17 00:00:00 2001
From: nobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date: Wed, 25 Oct 2017 05:44:38 +0000
Subject: [PATCH] io.c: write a newline together

* io.c (rb_io_puts): write a newline together at once for each
  argument.  based on the patch by rohitpaulk (Rohit Kuruvilla) at
  [ruby-core:83508].  [Feature #14042]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60417 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
---
 io.c                           | 20 ++++++++++++++++----
 spec/ruby/core/io/puts_spec.rb |  9 +++------
 test/ruby/test_io.rb           | 13 +++++++++++++
 3 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/io.c b/io.c
index 818399271e..03cd40e7e8 100644
--- a/io.c
+++ b/io.c
@@ -1686,6 +1686,16 @@ rb_io_write(VALUE io, VALUE str)
     return rb_funcallv(io, id_write, 1, &str);
 }
 
+static VALUE
+rb_io_writev(VALUE io, int argc, VALUE *argv)
+{
+    if (argc > 1 && rb_obj_method_arity(io, id_write) == 1) {
+	do rb_io_write(io, *argv++); while (--argc);
+	return argv[0];		/* unused right now */
+    }
+    return rb_funcallv(io, id_write, argc, argv);
+}
+
 /*
  *  call-seq:
  *     ios << obj     -> ios
@@ -7572,8 +7582,8 @@ io_puts_ary(VALUE ary, VALUE out, int recur)
 VALUE
 rb_io_puts(int argc, const VALUE *argv, VALUE out)
 {
-    int i;
-    VALUE line;
+    int i, n;
+    VALUE line, args[2];
 
     /* if no argument given, print newline. */
     if (argc == 0) {
@@ -7590,11 +7600,13 @@ rb_io_puts(int argc, const VALUE *argv, VALUE out)
 	}
 	line = rb_obj_as_string(argv[i]);
       string:
-	rb_io_write(out, line);
+	n = 0;
+	args[n++] = line;
 	if (RSTRING_LEN(line) == 0 ||
             !rb_str_end_with_asciichar(line, '\n')) {
-	    rb_io_write(out, rb_default_rs);
+	    args[n++] = rb_default_rs;
 	}
+	rb_io_writev(out, n, args);
     }
 
     return Qnil;
diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb
index 8f2f4f2c81..51240eed73 100644
--- a/spec/ruby/core/io/puts_spec.rb
+++ b/spec/ruby/core/io/puts_spec.rb
@@ -43,18 +43,16 @@ describe "IO#puts" do
     object.should_receive(:method_missing).with(:to_ary)
     object.should_receive(:to_s).and_return("#<Object:0x...>")
 
-    @io.should_receive(:write).with("#<Object:0x...>")
-    @io.should_receive(:write).with("\n")
     @io.puts(object).should == nil
+    ScratchPad.recorded.should == "#<Object:0x...>\n"
   end
 
   it "calls :to_ary before writing non-string objects" do
     object = mock('hola')
     object.should_receive(:to_ary).and_return(["hola"])
 
-    @io.should_receive(:write).with("hola")
-    @io.should_receive(:write).with("\n")
     @io.puts(object).should == nil
+    ScratchPad.recorded.should == "hola\n"
   end
 
   it "calls :to_s before writing non-string objects that don't respond to :to_ary" do
@@ -69,9 +67,8 @@ describe "IO#puts" do
     object = mock('hola')
     object.should_receive(:to_s).and_return(false)
 
-    @io.should_receive(:write).with(object.inspect.split(" ")[0] + ">")
-    @io.should_receive(:write).with("\n")
     @io.puts(object).should == nil
+    ScratchPad.recorded.should == object.inspect.split(" ")[0] + ">\n"
   end
 
   it "writes each arg if given several" do
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index c7de83784b..a32369eca5 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -2445,6 +2445,19 @@ End
     end)
   end
 
+  def test_puts_parallel
+    pipe(proc do |w|
+      threads = []
+      100.times do
+        threads << Thread.new { w.puts "hey" }
+      end
+      threads.each(&:join)
+      w.close
+    end, proc do |r|
+      assert_equal("hey\n" * 100, r.read)
+    end)
+  end
+
   def test_display
     pipe(proc do |w|
       "foo".display(w)