polyphony 1.5 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +14 -0
  4. data/TODO.md +0 -4
  5. data/ext/polyphony/backend_io_uring.c +34 -1
  6. data/ext/polyphony/backend_io_uring_context.c +24 -18
  7. data/ext/polyphony/backend_io_uring_context.h +4 -2
  8. data/ext/polyphony/backend_libev.c +4 -7
  9. data/ext/polyphony/event.c +21 -0
  10. data/ext/polyphony/extconf.rb +20 -18
  11. data/ext/polyphony/fiber.c +0 -2
  12. data/ext/polyphony/polyphony.c +2 -0
  13. data/ext/polyphony/polyphony.h +5 -0
  14. data/ext/polyphony/ring_buffer.c +1 -0
  15. data/ext/polyphony/runqueue_ring_buffer.c +1 -0
  16. data/ext/polyphony/thread.c +63 -0
  17. data/lib/polyphony/adapters/open3.rb +190 -0
  18. data/lib/polyphony/core/sync.rb +83 -13
  19. data/lib/polyphony/core/timer.rb +7 -25
  20. data/lib/polyphony/extensions/exception.rb +15 -0
  21. data/lib/polyphony/extensions/fiber.rb +14 -13
  22. data/lib/polyphony/extensions/io.rb +56 -14
  23. data/lib/polyphony/extensions/kernel.rb +1 -1
  24. data/lib/polyphony/extensions/object.rb +1 -13
  25. data/lib/polyphony/extensions/process.rb +76 -1
  26. data/lib/polyphony/extensions/thread.rb +19 -27
  27. data/lib/polyphony/version.rb +1 -1
  28. data/lib/polyphony.rb +11 -5
  29. data/test/helper.rb +46 -4
  30. data/test/open3/envutil.rb +380 -0
  31. data/test/open3/find_executable.rb +24 -0
  32. data/test/stress.rb +11 -7
  33. data/test/test_backend.rb +7 -2
  34. data/test/test_event.rb +10 -3
  35. data/test/test_ext.rb +2 -1
  36. data/test/test_fiber.rb +16 -4
  37. data/test/test_global_api.rb +13 -12
  38. data/test/test_io.rb +39 -0
  39. data/test/test_kernel.rb +2 -2
  40. data/test/test_monitor.rb +356 -0
  41. data/test/test_open3.rb +338 -0
  42. data/test/test_signal.rb +5 -1
  43. data/test/test_socket.rb +6 -3
  44. data/test/test_sync.rb +46 -0
  45. data/test/test_thread.rb +10 -1
  46. data/test/test_thread_pool.rb +5 -0
  47. data/test/test_throttler.rb +1 -1
  48. data/test/test_timer.rb +8 -2
  49. data/test/test_trace.rb +2 -0
  50. data/vendor/liburing/.github/workflows/build.yml +8 -0
  51. data/vendor/liburing/.gitignore +1 -0
  52. data/vendor/liburing/CHANGELOG +8 -0
  53. data/vendor/liburing/configure +17 -25
  54. data/vendor/liburing/debian/liburing-dev.manpages +2 -0
  55. data/vendor/liburing/debian/rules +2 -1
  56. data/vendor/liburing/examples/Makefile +2 -1
  57. data/vendor/liburing/examples/io_uring-udp.c +11 -3
  58. data/vendor/liburing/examples/rsrc-update-bench.c +100 -0
  59. data/vendor/liburing/liburing.spec +1 -1
  60. data/vendor/liburing/make-debs.sh +4 -2
  61. data/vendor/liburing/src/Makefile +5 -5
  62. data/vendor/liburing/src/arch/aarch64/lib.h +1 -1
  63. data/vendor/liburing/src/include/liburing/io_uring.h +41 -16
  64. data/vendor/liburing/src/include/liburing.h +86 -11
  65. data/vendor/liburing/src/int_flags.h +1 -0
  66. data/vendor/liburing/src/liburing-ffi.map +12 -0
  67. data/vendor/liburing/src/liburing.map +8 -0
  68. data/vendor/liburing/src/register.c +7 -2
  69. data/vendor/liburing/src/setup.c +373 -81
  70. data/vendor/liburing/test/232c93d07b74.c +3 -3
  71. data/vendor/liburing/test/Makefile +10 -3
  72. data/vendor/liburing/test/accept.c +2 -1
  73. data/vendor/liburing/test/buf-ring.c +35 -75
  74. data/vendor/liburing/test/connect-rep.c +204 -0
  75. data/vendor/liburing/test/coredump.c +59 -0
  76. data/vendor/liburing/test/fallocate.c +9 -0
  77. data/vendor/liburing/test/fd-pass.c +34 -3
  78. data/vendor/liburing/test/file-verify.c +27 -6
  79. data/vendor/liburing/test/helpers.c +3 -1
  80. data/vendor/liburing/test/io_uring_register.c +25 -28
  81. data/vendor/liburing/test/io_uring_setup.c +1 -1
  82. data/vendor/liburing/test/poll-cancel-all.c +29 -5
  83. data/vendor/liburing/test/poll-race-mshot.c +6 -22
  84. data/vendor/liburing/test/read-write.c +53 -0
  85. data/vendor/liburing/test/recv-msgall.c +21 -23
  86. data/vendor/liburing/test/reg-fd-only.c +55 -0
  87. data/vendor/liburing/test/reg-hint.c +56 -0
  88. data/vendor/liburing/test/regbuf-merge.c +91 -0
  89. data/vendor/liburing/test/ringbuf-read.c +2 -10
  90. data/vendor/liburing/test/send_recvmsg.c +5 -16
  91. data/vendor/liburing/test/shutdown.c +2 -1
  92. data/vendor/liburing/test/socket-io-cmd.c +215 -0
  93. data/vendor/liburing/test/socket-rw-eagain.c +2 -1
  94. data/vendor/liburing/test/socket-rw-offset.c +2 -1
  95. data/vendor/liburing/test/socket-rw.c +2 -1
  96. data/vendor/liburing/test/timeout.c +276 -0
  97. data/vendor/liburing/test/xattr.c +38 -25
  98. metadata +14 -3
  99. data/vendor/liburing/test/timeout-overflow.c +0 -204
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from https://github.com/ruby/open3/blob/master/lib/open3.rb
4
+
5
+ module Open3
6
+
7
+ # Open3.capture3 captures the standard output and the standard error of a command.
8
+ #
9
+ # stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
10
+ #
11
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
12
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
13
+ #
14
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
15
+ #
16
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
17
+ #
18
+ # Examples:
19
+ #
20
+ # # dot is a command of graphviz.
21
+ # graph = <<'End'
22
+ # digraph g {
23
+ # a -> b
24
+ # }
25
+ # End
26
+ # drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
27
+ #
28
+ # o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
29
+ # p o #=> "abc\n"
30
+ # p e #=> "bar\nbaz\nfoo\n"
31
+ # p s #=> #<Process::Status: pid 32682 exit 0>
32
+ #
33
+ # # generate a thumbnail image using the convert command of ImageMagick.
34
+ # # However, if the image is really stored in a file,
35
+ # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
36
+ # # because of reduced memory consumption.
37
+ # # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
38
+ # # Open3.capture3 should be considered.
39
+ # #
40
+ # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
41
+ # thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
42
+ # if s.success?
43
+ # STDOUT.binmode; print thumbnail
44
+ # end
45
+ #
46
+ def capture3(*cmd)
47
+ if Hash === cmd.last
48
+ opts = cmd.pop.dup
49
+ else
50
+ opts = {}
51
+ end
52
+
53
+ stdin_data = opts.delete(:stdin_data) || ''
54
+ binmode = opts.delete(:binmode)
55
+
56
+ popen3(*cmd, opts) {|i, o, e, t|
57
+ if binmode
58
+ i.binmode
59
+ o.binmode
60
+ e.binmode
61
+ end
62
+ out_reader = spin { o.read }
63
+ err_reader = spin { e.read }
64
+ begin
65
+ if stdin_data.respond_to? :readpartial
66
+ IO.double_splice(stdin_data, i)
67
+ else
68
+ i.write stdin_data
69
+ end
70
+ rescue Errno::EPIPE
71
+ end
72
+ i.close
73
+ [out_reader.value, err_reader.value, t.value]
74
+ }
75
+ end
76
+ module_function :capture3
77
+
78
+ # Open3.capture2 captures the standard output of a command.
79
+ #
80
+ # stdout_str, status = Open3.capture2([env,] cmd... [, opts])
81
+ #
82
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
83
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
84
+ #
85
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
86
+ #
87
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
88
+ #
89
+ # Example:
90
+ #
91
+ # # factor is a command for integer factorization.
92
+ # o, s = Open3.capture2("factor", :stdin_data=>"42")
93
+ # p o #=> "42: 2 3 7\n"
94
+ #
95
+ # # generate x**2 graph in png using gnuplot.
96
+ # gnuplot_commands = <<"End"
97
+ # set terminal png
98
+ # plot x**2, "-" with lines
99
+ # 1 14
100
+ # 2 1
101
+ # 3 8
102
+ # 4 5
103
+ # e
104
+ # End
105
+ # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
106
+ #
107
+ def capture2(*cmd)
108
+ if Hash === cmd.last
109
+ opts = cmd.pop.dup
110
+ else
111
+ opts = {}
112
+ end
113
+
114
+ stdin_data = opts.delete(:stdin_data)
115
+ binmode = opts.delete(:binmode)
116
+
117
+ popen2(*cmd, opts) {|i, o, t|
118
+ if binmode
119
+ i.binmode
120
+ o.binmode
121
+ end
122
+ out_reader = spin { o.read }
123
+ if stdin_data
124
+ begin
125
+ if stdin_data.respond_to? :readpartial
126
+ IO.double_splice(stdin_data, i)
127
+ else
128
+ i.write stdin_data
129
+ end
130
+ rescue Errno::EPIPE
131
+ end
132
+ end
133
+ i.close
134
+ [out_reader.value, t.value]
135
+ }
136
+ end
137
+ module_function :capture2
138
+
139
+ # Open3.capture2e captures the standard output and the standard error of a command.
140
+ #
141
+ # stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
142
+ #
143
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
144
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
145
+ #
146
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
147
+ #
148
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
149
+ #
150
+ # Example:
151
+ #
152
+ # # capture make log
153
+ # make_log, s = Open3.capture2e("make")
154
+ #
155
+ def capture2e(*cmd)
156
+ if Hash === cmd.last
157
+ opts = cmd.pop.dup
158
+ else
159
+ opts = {}
160
+ end
161
+
162
+ stdin_data = opts.delete(:stdin_data)
163
+ binmode = opts.delete(:binmode)
164
+
165
+ popen2e(*cmd, opts) {|i, oe, t|
166
+ if binmode
167
+ i.binmode
168
+ oe.binmode
169
+ end
170
+ outerr_reader = spin { oe.read }
171
+ if stdin_data
172
+ begin
173
+ if stdin_data.respond_to? :readpartial
174
+ IO.double_splice(stdin_data, i)
175
+ else
176
+ i.write stdin_data
177
+ end
178
+ rescue Errno::EPIPE
179
+ # ignore
180
+ end
181
+ end
182
+ i.close
183
+ [outerr_reader.value, t.value]
184
+ }
185
+ end
186
+ module_function :capture2e
187
+ end
188
+
189
+ # JRuby uses different popen logic on Windows, require it here to reuse wrapper methods above.
190
+ require 'open3/jruby_windows' if RUBY_ENGINE == 'jruby' && JRuby::Util::ON_WINDOWS
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'monitor'
4
+
3
5
  module Polyphony
4
6
  # Implements mutex lock for synchronizing access to a shared resource. This
5
7
  # class replaces the stock `Thread::Mutex` class.
@@ -27,8 +29,7 @@ module Polyphony
27
29
  #
28
30
  # @return [nil]
29
31
  def conditional_release
30
- @store << @token
31
- @token = nil
32
+ @store << true
32
33
  @holding_fiber = nil
33
34
  end
34
35
 
@@ -37,7 +38,7 @@ module Polyphony
37
38
  #
38
39
  # @return [Fiber] current fiber
39
40
  def conditional_reacquire
40
- @token = @store.shift
41
+ @store.shift
41
42
  @holding_fiber = Fiber.current
42
43
  end
43
44
 
@@ -60,9 +61,10 @@ module Polyphony
60
61
  #
61
62
  # @return [Mutex] self
62
63
  def lock
64
+ check_dead_holder
63
65
  raise ThreadError if owned?
64
66
 
65
- @token = @store.shift
67
+ @store.shift
66
68
  @holding_fiber = Fiber.current
67
69
  self
68
70
  end
@@ -75,8 +77,7 @@ module Polyphony
75
77
  raise ThreadError if !owned?
76
78
 
77
79
  @holding_fiber = nil
78
- @store << @token if @token
79
- @token = nil
80
+ @store << true
80
81
  end
81
82
 
82
83
  # Attempts to obtain the lock and returns immediately. Returns `true` if the
@@ -86,7 +87,7 @@ module Polyphony
86
87
  def try_lock
87
88
  return false if @holding_fiber
88
89
 
89
- @token = @store.shift
90
+ @store.shift
90
91
  @holding_fiber = Fiber.current
91
92
  true
92
93
  end
@@ -114,16 +115,79 @@ module Polyphony
114
115
  #
115
116
  # @return [any] return value of given block.
116
117
  def synchronize_not_holding
117
- @token = @store.shift
118
+ @store.shift
118
119
  begin
119
120
  @holding_fiber = Fiber.current
120
121
  yield
121
122
  ensure
122
123
  @holding_fiber = nil
123
- @store << @token if @token
124
- @token = nil
124
+ @store << true
125
+ end
126
+ end
127
+
128
+ def check_dead_holder
129
+ return if !@holding_fiber&.dead?
130
+
131
+ @holding_fiber = nil
132
+ @store << true
133
+ end
134
+ end
135
+
136
+ # Implements a fiber-aware Monitor class. This class replaces the stock
137
+ # `Monitor` class.
138
+ class Monitor < Mutex
139
+ def enter
140
+ if @holding_fiber == Fiber.current
141
+ @holding_count += 1
142
+ else
143
+ lock
144
+ @holding_count = 1
145
+ end
146
+ end
147
+ alias_method :mon_enter, :enter
148
+
149
+ def exit
150
+ raise ThreadError if !owned?
151
+
152
+ @holding_count -= 1
153
+ unlock if @holding_count == 0
154
+ end
155
+ alias_method :mon_exit, :exit
156
+
157
+ def mon_check_owner
158
+ if Fiber.current == @holding_fiber
159
+ nil
160
+ else
161
+ raise ThreadError, 'current fiber not owner'
125
162
  end
126
163
  end
164
+
165
+ def mon_locked?
166
+ !!@holding_fiber
167
+ end
168
+
169
+ def mon_owned?
170
+ @holding_fiber == Fiber.current
171
+ end
172
+
173
+ alias_method :mon_synchronize, :synchronize
174
+
175
+ def new_cond
176
+ MonitorMixin::ConditionVariable.new(self)
177
+ end
178
+
179
+ def try_enter
180
+ check_dead_holder
181
+ return false if @holding_fiber
182
+
183
+ enter
184
+ true
185
+ end
186
+ alias_method :try_mon_enter, :try_enter
187
+
188
+ def wait_for_cond(cond, timeout)
189
+ cond.wait(self, timeout)
190
+ end
127
191
  end
128
192
 
129
193
  # Implements a fiber-aware ConditionVariable
@@ -136,12 +200,18 @@ module Polyphony
136
200
  # Waits for the condition variable to be signalled.
137
201
  #
138
202
  # @param mutex [Polyphony::Mutex] mutex to release while waiting for signal
139
- # @param _timeout [Number, nil] timeout in seconds (currently not implemented)
203
+ # @param timeout [Number, nil] timeout in seconds, or nil for no timeout
140
204
  # @return [any]
141
- def wait(mutex, _timeout = nil)
205
+ def wait(mutex, timeout = nil)
142
206
  mutex.conditional_release
143
207
  @queue << Fiber.current
144
- Polyphony.backend_wait_event(true)
208
+ if timeout
209
+ move_on_after(timeout, with_value: false) { Polyphony.backend_wait_event(true); true }
210
+ else
211
+ Polyphony.backend_wait_event(true)
212
+ true
213
+ end
214
+ ensure
145
215
  mutex.conditional_reacquire
146
216
  end
147
217
 
@@ -192,38 +192,20 @@ module Polyphony
192
192
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
193
193
  end
194
194
 
195
- # Converts a timeout record's exception spec to an exception instance.
196
- #
197
- # @param record [Array, Class, Exception, String] exception spec
198
- # @return [Exception] exception instance
199
- def timeout_exception(record)
200
- case (exception = record[:exception])
201
- when Array
202
- exception[0].new(exception[1])
203
- when Class
204
- exception.new
205
- when Exception
206
- exception
207
- else
208
- RuntimeError.new(exception)
209
- end
210
- end
211
-
212
195
  # Runs a timer iteration, invoking any timeouts that are due.
213
196
  def update
214
197
  return if @timeouts.empty?
215
198
 
216
- @timeouts.each do |fiber, record|
217
- next if record[:target_stamp] > now
199
+ @timeouts.each do |f, r|
200
+ next if r[:target_stamp] > now
218
201
 
219
- value = record[:exception] ? timeout_exception(record) : record[:value]
220
- fiber.schedule value
202
+ f.schedule(
203
+ r[:exception] ? Exception.instantiate(r[:exception]) : r[:value]
204
+ )
221
205
 
222
- next unless record[:recurring]
206
+ next if !r[:recurring]
223
207
 
224
- while record[:target_stamp] <= now
225
- record[:target_stamp] += record[:interval]
226
- end
208
+ r[:target_stamp] += r[:interval] while r[:target_stamp] <= now
227
209
  end
228
210
  end
229
211
  end
@@ -6,6 +6,21 @@ class ::Exception
6
6
  # Set to true to disable sanitizing the backtrace (to remove frames occuring
7
7
  # in the Polyphony code itself.)
8
8
  attr_accessor :__disable_sanitized_backtrace__
9
+
10
+ # Creates an exception instance from the given error according to its type.
11
+ #
12
+ # @param error [nil, String, Class, Exception] error
13
+ # @param message [nil, String] error message
14
+ # @return [Exception] exception instance
15
+ def instantiate(error = nil, message = nil)
16
+ case error
17
+ when String then RuntimeError.new(error)
18
+ when Array then error[0].new(error[1])
19
+ when Class then error.new(message)
20
+ when Exception then error
21
+ else RuntimeError.new
22
+ end
23
+ end
9
24
  end
10
25
 
11
26
  # Set to the fiber in which the exception was *originally* raised (in case the
@@ -167,7 +167,7 @@ class ::Fiber
167
167
  # @param any [Exception] exception to raise
168
168
  # @return [Fiber] self
169
169
  def raise(*args)
170
- error = error_from_raise_args(args)
170
+ error = Exception.instantiate(*args)
171
171
  schedule(error)
172
172
  self
173
173
  end
@@ -182,13 +182,24 @@ class ::Fiber
182
182
  raise Polyphony::Interjection.new(block)
183
183
  end
184
184
 
185
- # Blocks until the fiber has terminated, returning its return value.
185
+ # Waits for the fiber to terminate, and returns its return value (the result
186
+ # of its last statement). If the fiber has terminated with an ancaught
187
+ # exception, the exception will be raised.
186
188
  #
187
- # @return [any] fiber's return value
189
+ # f = spin { :foo; :bar }
190
+ # f.await #=> :bar
191
+ #
192
+ # @overload await
193
+ # @return [any] fiber's return value
194
+ # @overload join
195
+ # @return [any] fiber's return value
196
+ # @overload value
197
+ # @return [any] fiber's return value
188
198
  def await
189
199
  Fiber.await(self).first
190
200
  end
191
201
  alias_method :join, :await
202
+ alias_method :value, :await
192
203
 
193
204
  #############################
194
205
  # Fiber supervision methods #
@@ -648,16 +659,6 @@ class ::Fiber
648
659
 
649
660
  private
650
661
 
651
- # @!visibility private
652
- def error_from_raise_args(args)
653
- case (arg = args.shift)
654
- when String then RuntimeError.new(arg)
655
- when Class then arg.new(args.shift)
656
- when Exception then arg
657
- else RuntimeError.new
658
- end
659
- end
660
-
661
662
  # @!visibility private
662
663
  def supervise_opts_to_block(opts)
663
664
  block = opts[:on_done] || opts[:on_error]
@@ -59,12 +59,12 @@ class ::IO
59
59
  end
60
60
  end
61
61
 
62
- # alias_method :orig_readlines, :readlines
63
- # def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
64
- # File.open(name, 'r') do |f|
65
- # f.readlines(sep, limit, getline_args)
66
- # end
67
- # end
62
+ alias_method :orig_readlines, :readlines
63
+ def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
64
+ File.open(name, 'r') do |f|
65
+ f.readlines(sep, **getline_args)
66
+ end
67
+ end
68
68
 
69
69
  # @!visibility private
70
70
  alias_method :orig_write, :write
@@ -87,6 +87,42 @@ class ::IO
87
87
  Open3.popen2(cmd) { |_i, o, _t| yield o }
88
88
  end
89
89
 
90
+ def copy_stream(src, dst, src_length = nil, src_offset = 0)
91
+ close_src = false
92
+ close_dst = false
93
+ if !src.respond_to?(:readpartial)
94
+ src = File.open(src, 'r+')
95
+ close_src = true
96
+ end
97
+ if !dst.respond_to?(:readpartial)
98
+ dst = File.open(dst, 'w+')
99
+ close_dst = true
100
+ end
101
+ src.seek(src_offset) if src_offset > 0
102
+
103
+ pipe = Polyphony::Pipe.new
104
+
105
+ pipe_to_dst = spin { dst.splice_from(pipe, -65536) }
106
+
107
+ count = 0
108
+ if src_length
109
+ while count < src_length
110
+ count += pipe.splice_from(src, src_length)
111
+ end
112
+ else
113
+ count = pipe.splice_from(src, -65536)
114
+ end
115
+
116
+ pipe.close
117
+ pipe_to_dst.await
118
+
119
+ count
120
+ ensure
121
+ pipe_to_dst&.stop
122
+ src.close if close_src
123
+ dst.close if close_dst
124
+ end
125
+
90
126
  # Splices from one IO to another IO. At least one of the IOs must be a pipe.
91
127
  # If maxlen is negative, splices repeatedly using absolute value of maxlen
92
128
  # until EOF is encountered.
@@ -109,6 +145,17 @@ class ::IO
109
145
  Polyphony.backend_double_splice(src, dest)
110
146
  end
111
147
 
148
+ if !Polyphony.respond_to?(:backend_double_splice)
149
+ def double_splice(src, dest)
150
+ pipe = Polyphony::Pipe.new
151
+ f = spin { Polyphony.backend_splice(pipe, dest, -65536) }
152
+ Polyphony.backend_splice(src, pipe, -65536)
153
+ pipe.close
154
+ ensure
155
+ f.stop
156
+ end
157
+ end
158
+
112
159
  # Tees data from the source to the desination.
113
160
  #
114
161
  # @param src [IO, Polyphony::Pipe] source to tee from
@@ -120,11 +167,6 @@ class ::IO
120
167
  end
121
168
 
122
169
  if RUBY_PLATFORM !~ /linux/
123
- # @!visibility private
124
- def double_splice(src, dest)
125
- raise NotImplementedError
126
- end
127
-
128
170
  # @!visibility private
129
171
  def tee(src, dest, maxlen)
130
172
  raise NotImplementedError
@@ -203,7 +245,7 @@ class ::IO
203
245
 
204
246
  @read_buffer ||= +''
205
247
  result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
206
- return nil unless result
248
+ return '' unless result
207
249
 
208
250
  already_read = @read_buffer
209
251
  @read_buffer = +''
@@ -280,7 +322,7 @@ class ::IO
280
322
  yield line
281
323
  end
282
324
 
283
- result = readpartial(8192, @read_buffer, -1)
325
+ result = Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
284
326
  return self if !result
285
327
  end
286
328
  rescue EOFError
@@ -436,7 +478,7 @@ class ::IO
436
478
  def close
437
479
  return if closed?
438
480
 
439
- Polyphony.backend_close(self)
481
+ Polyphony.backend_close(self) rescue nil
440
482
  nil
441
483
  end
442
484
 
@@ -123,7 +123,7 @@ module ::Kernel
123
123
  i.close
124
124
  pipe_to_eof(o, $stdout)
125
125
  end
126
- waiter.await.last == 0
126
+ waiter.value.success?
127
127
  rescue SystemCallError
128
128
  nil
129
129
  end
@@ -240,7 +240,7 @@ class ::Object
240
240
  fiber = Fiber.current
241
241
  canceller = spin do
242
242
  Polyphony.backend_sleep(interval)
243
- exception = cancel_exception(exception)
243
+ exception = Exception.instantiate(exception)
244
244
  exception.raising_fiber = Fiber.current
245
245
  fiber.cancel(exception)
246
246
  end
@@ -249,18 +249,6 @@ class ::Object
249
249
  canceller.stop
250
250
  end
251
251
 
252
- # Converts the given exception spec to an exception instance.
253
- #
254
- # @param exception [Exception, Class, Array<class, message>] exception spec
255
- # @return [Exception] exception instance
256
- def cancel_exception(exception)
257
- case exception
258
- when Class then exception.new
259
- when Array then exception[0].new(exception[1])
260
- else RuntimeError.new(exception)
261
- end
262
- end
263
-
264
252
  # Helper method for performing `#spin_loop` without throttling. Spins up a
265
253
  # new fiber in which to run the loop.
266
254
  #