From 43c7c9f194e31f615c69352dcdc441da8dbd4225 Mon Sep 17 00:00:00 2001 From: Julian Scheid Date: Fri, 13 Dec 2024 18:04:08 +0100 Subject: [PATCH] Fix streaming to file descriptor (#945) * Fix streaming to file descriptor Don't use write(2) for writing to file descriptors, use IO::write instead just as for writing to a non-file IO. Using write(2) means having to handle all kinds of edge cases, such as EINTR and EAGAIN/EWOULDBLOCK, and doesn't release the GVL when blocking. * Skip test_stream_writer_subprocess on Windows --- ext/oj/stream_writer.c | 8 ++------ test/test_writer.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ext/oj/stream_writer.c b/ext/oj/stream_writer.c index a1cff080..7724b6e6 100644 --- a/ext/oj/stream_writer.c +++ b/ext/oj/stream_writer.c @@ -42,7 +42,8 @@ static void stream_writer_write(StreamWriter sw) { switch (sw->type) { case STRING_IO: - case STREAM_IO: { + case STREAM_IO: + case FILE_IO: { volatile VALUE rs = rb_str_new(sw->sw.out.buf, size); // Oddly enough, when pushing ASCII characters with UTF-8 encoding or @@ -53,11 +54,6 @@ static void stream_writer_write(StreamWriter sw) { rb_funcall(sw->stream, oj_write_id, 1, rs); break; } - case FILE_IO: - if (size != write(sw->fd, sw->sw.out.buf, size)) { - rb_raise(rb_eIOError, "Write failed. [_%d_:%s]\n", errno, strerror(errno)); - } - break; default: rb_raise(rb_eArgError, "expected an IO Object."); } stream_writer_reset_buf(sw); diff --git a/test/test_writer.rb b/test/test_writer.rb index dfb81d97..34463fde 100755 --- a/test/test_writer.rb +++ b/test/test_writer.rb @@ -4,6 +4,7 @@ $LOAD_PATH << __dir__ require 'helper' +require 'open3' class OjWriter < Minitest::Test @@ -377,4 +378,19 @@ def test_stream_writer_push_null_value_with_key w.pop() assert_equal(%|{"nothing":null}\n|, output.string()) end + + def test_stream_writer_subprocess + skip if RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ + + Open3.popen3("/bin/bash", "-c", "cat > /dev/null") do |stdin, _stdout, _stderr, _wait_thr| + w = Oj::StreamWriter.new(stdin, :indent => 0) + w.push_array() + chunk = "{\"foo\":\"#{"bar"*1000}\"}" + 1000.times do |_| + w.push_json(chunk) + end + w.pop() + stdin.close + end + end end # OjWriter