ruby-changes:69372
From: rm155 <ko1@a...>
Date: Sun, 24 Oct 2021 05:58:14 +0900 (JST)
Subject: [ruby-changes:69372] ee948fc1b4 (master): [ruby/csv] Add support for Ractor (https://github.com/ruby/csv/pull/218)
https://git.ruby-lang.org/ruby.git/commit/?id=ee948fc1b4 From ee948fc1b4cb1ad382beee709008bb93b8f6ba75 Mon Sep 17 00:00:00 2001 From: rm155 <86454369+rm155@u...> Date: Sun, 10 Oct 2021 22:21:42 -0400 Subject: [ruby/csv] Add support for Ractor (https://github.com/ruby/csv/pull/218) https://github.com/ruby/csv/commit/a802690e11 --- lib/csv.rb | 21 +++++++++++++++++++++ lib/csv/csv.gemspec | 2 +- lib/csv/parser.rb | 11 ++++++----- test/csv/interface/test_read.rb | 32 ++++++++++++++++++++++++++++++++ test/csv/interface/test_write.rb | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/lib/csv.rb b/lib/csv.rb index 87c3a4be31..42e99435cb 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -536,6 +536,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L536 # # There is no such storage structure for write headers. # +# In order for the parsing methods to access stored converters in non-main-Ractors, the +# storage structure must be made shareable first. +# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and +# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation +# of Ractors that use the converters stored in these structures. (Since making the storage +# structures shareable involves freezing them, any custom converters that are to be used +# must be added first.) +# # ===== Converter Lists # # A _converter_ _list_ is an \Array that may include any assortment of: @@ -908,6 +916,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L916 gsub(/\s+/, "_").to_sym } } + # Default values for method options. DEFAULT_OPTIONS = { # For both parsing and generating. @@ -946,6 +955,8 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L955 # Creates or retrieves cached \CSV objects. # For arguments and options, see CSV.new. # + # This API is not Ractor-safe. + # # --- # # With no block given, returns a \CSV object. @@ -1873,6 +1884,10 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1884 # csv.converters # => [:integer] # csv.convert(proc {|x| x.to_s }) # csv.converters + # + # Notes that you need to call + # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use + # this method. def converters parser_fields_converter.map do |converter| name = Converters.rassoc(converter) @@ -1935,6 +1950,10 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L1950 # Returns an \Array containing header converters; used for parsing; # see {Header Converters}[#class-CSV-label-Header+Converters]: # CSV.new('').header_converters # => [] + # + # Notes that you need to call + # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor + # to use this method. def header_converters header_fields_converter.map do |converter| name = HeaderConverters.rassoc(converter) @@ -2655,6 +2674,8 @@ end https://github.com/ruby/ruby/blob/trunk/lib/csv.rb#L2674 # io = StringIO.new # CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] } # +# This API is not Ractor-safe. +# def CSV(*args, **options, &block) CSV.instance(*args, **options, &block) end diff --git a/lib/csv/csv.gemspec b/lib/csv/csv.gemspec index 6948e9a72f..11c5b0f2a6 100644 --- a/lib/csv/csv.gemspec +++ b/lib/csv/csv.gemspec @@ -60,5 +60,5 @@ Gem::Specification.new do |spec| https://github.com/ruby/ruby/blob/trunk/lib/csv/csv.gemspec#L60 spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "benchmark_driver" - spec.add_development_dependency "test-unit", ">= 3.4.3" + spec.add_development_dependency "test-unit", ">= 3.4.8" end diff --git a/lib/csv/parser.rb b/lib/csv/parser.rb index 0d8a157fd7..3334acfbdd 100644 --- a/lib/csv/parser.rb +++ b/lib/csv/parser.rb @@ -480,9 +480,9 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv/parser.rb#L480 begin StringScanner.new("x").scan("x") rescue TypeError - @@string_scanner_scan_accept_string = false + STRING_SCANNER_SCAN_ACCEPT_STRING = false else - @@string_scanner_scan_accept_string = true + STRING_SCANNER_SCAN_ACCEPT_STRING = true end def prepare_separators @@ -506,7 +506,7 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv/parser.rb#L506 @first_column_separators = Regexp.new(@escaped_first_column_separator + "+".encode(@encoding)) else - if @@string_scanner_scan_accept_string + if STRING_SCANNER_SCAN_ACCEPT_STRING @column_end = @column_separator else @column_end = Regexp.new(@escaped_column_separator) @@ -725,6 +725,8 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv/parser.rb#L725 end end + SCANNER_TEST_CHUNK_SIZE = + Integer((ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"), 10) def build_scanner inputs = @samples.collect do |sample| UnoptimizedStringIO.new(sample) @@ -734,10 +736,9 @@ class CSV https://github.com/ruby/ruby/blob/trunk/lib/csv/parser.rb#L736 else inputs << @input end - chunk_size = ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1" InputsScanner.new(inputs, @encoding, - chunk_size: Integer(chunk_size, 10)) + chunk_size: SCANNER_TEST_CHUNK_SIZE) end else def build_scanner diff --git a/test/csv/interface/test_read.rb b/test/csv/interface/test_read.rb index b86c54fc9f..d73622d554 100644 --- a/test/csv/interface/test_read.rb +++ b/test/csv/interface/test_read.rb @@ -32,6 +32,24 @@ class TestCSVInterfaceRead < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/csv/interface/test_read.rb#L32 assert_equal(@rows, rows) end + if respond_to?(:ractor) + ractor + def test_foreach_in_ractor + ractor = Ractor.new(@input.path) do |path| + rows = [] + CSV.foreach(path, col_sep: "\t", row_sep: "\r\n").each do |row| + rows << row + end + rows + end + rows = [ + ["1", "2", "3"], + ["4", "5"], + ] + assert_equal(rows, ractor.take) + end + end + def test_foreach_mode rows = [] CSV.foreach(@input.path, "r", col_sep: "\t", row_sep: "\r\n").each do |row| @@ -240,6 +258,20 @@ class TestCSVInterfaceRead < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/csv/interface/test_read.rb#L258 CSV.read(@input.path, col_sep: "\t", row_sep: "\r\n")) end + if respond_to?(:ractor) + ractor + def test_read_in_ractor + ractor = Ractor.new(@input.path) do |path| + CSV.read(path, col_sep: "\t", row_sep: "\r\n") + end + rows = [ + ["1", "2", "3"], + ["4", "5"], + ] + assert_equal(rows, ractor.take) + end + end + def test_readlines assert_equal(@rows, CSV.readlines(@input.path, col_sep: "\t", row_sep: "\r\n")) diff --git a/test/csv/interface/test_write.rb b/test/csv/interface/test_write.rb index 8650ecd624..02c2c5c5ce 100644 --- a/test/csv/interface/test_write.rb +++ b/test/csv/interface/test_write.rb @@ -25,6 +25,21 @@ class TestCSVInterfaceWrite < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/csv/interface/test_write.rb#L25 CSV end + if respond_to?(:ractor) + ractor + def test_generate_default_in_ractor + ractor = Ractor.new do + CSV.generate do |csv| + csv << [1, 2, 3] << [4, nil, 5] + end + end + assert_equal(<<-CSV, ractor.take) +1,2,3 +4,,5 + CSV + end + end + def test_generate_append csv_text = <<-CSV 1,2,3 @@ -101,6 +116,25 @@ a,b,c https://github.com/ruby/ruby/blob/trunk/test/csv/interface/test_write.rb#L116 CSV end + + if respond_to?(:ractor) + ractor + def test_append_row_in_ractor + ractor = Ractor.new(@output.path) do |path| + CSV.open(path, "wb") do |csv| + csv << + CSV::Row.new([], ["1", "2", "3"]) << + CSV::Row.new([], ["a", "b", "c"]) + end + end + ractor.take + assert_equal(<<-CSV, File.read(@output.path, mode: "rb")) +1,2,3 +a,b,c + CSV + end + end + def test_append_hash CSV.open(@output.path, "wb", headers: true) do |csv| csv << [:a, :b, :c] -- cgit v1.2.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/