Skip to content

Commit a802690

Browse files
authored
Add support for Ractor (#218)
1 parent 29cef9e commit a802690

File tree

5 files changed

+94
-6
lines changed

5 files changed

+94
-6
lines changed

csv.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,5 @@ Gem::Specification.new do |spec|
6060
spec.add_development_dependency "bundler"
6161
spec.add_development_dependency "rake"
6262
spec.add_development_dependency "benchmark_driver"
63-
spec.add_development_dependency "test-unit", ">= 3.4.3"
63+
spec.add_development_dependency "test-unit", ">= 3.4.8"
6464
end

lib/csv.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,14 @@
547547
#
548548
# There is no such storage structure for write headers.
549549
#
550+
# In order for the parsing methods to access stored converters in non-main-Ractors, the
551+
# storage structure must be made shareable first.
552+
# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
553+
# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
554+
# of Ractors that use the converters stored in these structures. (Since making the storage
555+
# structures shareable involves freezing them, any custom converters that are to be used
556+
# must be added first.)
557+
#
550558
# ===== Converter Lists
551559
#
552560
# A _converter_ _list_ is an \Array that may include any assortment of:
@@ -919,6 +927,7 @@ def initialize(message, line_number)
919927
gsub(/\s+/, "_").to_sym
920928
}
921929
}
930+
922931
# Default values for method options.
923932
DEFAULT_OPTIONS = {
924933
# For both parsing and generating.
@@ -957,6 +966,8 @@ class << self
957966
# Creates or retrieves cached \CSV objects.
958967
# For arguments and options, see CSV.new.
959968
#
969+
# This API is not Ractor-safe.
970+
#
960971
# ---
961972
#
962973
# With no block given, returns a \CSV object.
@@ -1992,6 +2003,10 @@ def skip_lines
19922003
# csv.converters # => [:integer]
19932004
# csv.convert(proc {|x| x.to_s })
19942005
# csv.converters
2006+
#
2007+
# Notes that you need to call
2008+
# +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
2009+
# this method.
19952010
def converters
19962011
parser_fields_converter.map do |converter|
19972012
name = Converters.rassoc(converter)
@@ -2054,6 +2069,10 @@ def write_headers?
20542069
# Returns an \Array containing header converters; used for parsing;
20552070
# see {Header Converters}[#class-CSV-label-Header+Converters]:
20562071
# CSV.new('').header_converters # => []
2072+
#
2073+
# Notes that you need to call
2074+
# +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
2075+
# to use this method.
20572076
def header_converters
20582077
header_fields_converter.map do |converter|
20592078
name = HeaderConverters.rassoc(converter)
@@ -2774,6 +2793,8 @@ def writer_options
27742793
# io = StringIO.new
27752794
# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
27762795
#
2796+
# This API is not Ractor-safe.
2797+
#
27772798
def CSV(*args, **options, &block)
27782799
CSV.instance(*args, **options, &block)
27792800
end

lib/csv/parser.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,9 @@ def prepare_strip
480480
begin
481481
StringScanner.new("x").scan("x")
482482
rescue TypeError
483-
@@string_scanner_scan_accept_string = false
483+
STRING_SCANNER_SCAN_ACCEPT_STRING = false
484484
else
485-
@@string_scanner_scan_accept_string = true
485+
STRING_SCANNER_SCAN_ACCEPT_STRING = true
486486
end
487487

488488
def prepare_separators
@@ -506,7 +506,7 @@ def prepare_separators
506506
@first_column_separators = Regexp.new(@escaped_first_column_separator +
507507
"+".encode(@encoding))
508508
else
509-
if @@string_scanner_scan_accept_string
509+
if STRING_SCANNER_SCAN_ACCEPT_STRING
510510
@column_end = @column_separator
511511
else
512512
@column_end = Regexp.new(@escaped_column_separator)
@@ -725,6 +725,8 @@ def eof?
725725
end
726726
end
727727

728+
SCANNER_TEST_CHUNK_SIZE =
729+
Integer((ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"), 10)
728730
def build_scanner
729731
inputs = @samples.collect do |sample|
730732
UnoptimizedStringIO.new(sample)
@@ -734,10 +736,9 @@ def build_scanner
734736
else
735737
inputs << @input
736738
end
737-
chunk_size = ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"
738739
InputsScanner.new(inputs,
739740
@encoding,
740-
chunk_size: Integer(chunk_size, 10))
741+
chunk_size: SCANNER_TEST_CHUNK_SIZE)
741742
end
742743
else
743744
def build_scanner

test/csv/interface/test_read.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ def test_foreach
3232
assert_equal(@rows, rows)
3333
end
3434

35+
if respond_to?(:ractor)
36+
ractor
37+
def test_foreach_in_ractor
38+
ractor = Ractor.new(@input.path) do |path|
39+
rows = []
40+
CSV.foreach(path, col_sep: "\t", row_sep: "\r\n").each do |row|
41+
rows << row
42+
end
43+
rows
44+
end
45+
rows = [
46+
["1", "2", "3"],
47+
["4", "5"],
48+
]
49+
assert_equal(rows, ractor.take)
50+
end
51+
end
52+
3553
def test_foreach_mode
3654
rows = []
3755
CSV.foreach(@input.path, "r", col_sep: "\t", row_sep: "\r\n").each do |row|
@@ -250,6 +268,20 @@ def test_read
250268
CSV.read(@input.path, col_sep: "\t", row_sep: "\r\n"))
251269
end
252270

271+
if respond_to?(:ractor)
272+
ractor
273+
def test_read_in_ractor
274+
ractor = Ractor.new(@input.path) do |path|
275+
CSV.read(path, col_sep: "\t", row_sep: "\r\n")
276+
end
277+
rows = [
278+
["1", "2", "3"],
279+
["4", "5"],
280+
]
281+
assert_equal(rows, ractor.take)
282+
end
283+
end
284+
253285
def test_readlines
254286
assert_equal(@rows,
255287
CSV.readlines(@input.path, col_sep: "\t", row_sep: "\r\n"))

test/csv/interface/test_write.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ def test_generate_default
2525
CSV
2626
end
2727

28+
if respond_to?(:ractor)
29+
ractor
30+
def test_generate_default_in_ractor
31+
ractor = Ractor.new do
32+
CSV.generate do |csv|
33+
csv << [1, 2, 3] << [4, nil, 5]
34+
end
35+
end
36+
assert_equal(<<-CSV, ractor.take)
37+
1,2,3
38+
4,,5
39+
CSV
40+
end
41+
end
42+
2843
def test_generate_append
2944
csv_text = <<-CSV
3045
1,2,3
@@ -101,6 +116,25 @@ def test_append_row
101116
CSV
102117
end
103118

119+
120+
if respond_to?(:ractor)
121+
ractor
122+
def test_append_row_in_ractor
123+
ractor = Ractor.new(@output.path) do |path|
124+
CSV.open(path, "wb") do |csv|
125+
csv <<
126+
CSV::Row.new([], ["1", "2", "3"]) <<
127+
CSV::Row.new([], ["a", "b", "c"])
128+
end
129+
end
130+
ractor.take
131+
assert_equal(<<-CSV, File.read(@output.path, mode: "rb"))
132+
1,2,3
133+
a,b,c
134+
CSV
135+
end
136+
end
137+
104138
def test_append_hash
105139
CSV.open(@output.path, "wb", headers: true) do |csv|
106140
csv << [:a, :b, :c]

0 commit comments

Comments
 (0)