polyphony 1.4 → 1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +22 -0
  4. data/TODO.md +5 -14
  5. data/examples/pipes/http_server.rb +42 -12
  6. data/examples/pipes/http_server2.rb +45 -0
  7. data/ext/polyphony/backend_common.h +5 -0
  8. data/ext/polyphony/backend_io_uring.c +174 -121
  9. data/ext/polyphony/backend_io_uring_context.c +24 -18
  10. data/ext/polyphony/backend_io_uring_context.h +4 -2
  11. data/ext/polyphony/backend_libev.c +46 -22
  12. data/ext/polyphony/event.c +21 -0
  13. data/ext/polyphony/extconf.rb +25 -19
  14. data/ext/polyphony/fiber.c +0 -2
  15. data/ext/polyphony/pipe.c +1 -1
  16. data/ext/polyphony/polyphony.c +2 -20
  17. data/ext/polyphony/polyphony.h +5 -5
  18. data/ext/polyphony/ring_buffer.c +1 -0
  19. data/ext/polyphony/runqueue_ring_buffer.c +1 -0
  20. data/ext/polyphony/thread.c +63 -0
  21. data/ext/polyphony/win_uio.h +18 -0
  22. data/lib/polyphony/adapters/open3.rb +190 -0
  23. data/lib/polyphony/core/sync.rb +83 -13
  24. data/lib/polyphony/core/timer.rb +7 -25
  25. data/lib/polyphony/extensions/exception.rb +15 -0
  26. data/lib/polyphony/extensions/fiber.rb +14 -13
  27. data/lib/polyphony/extensions/io.rb +56 -14
  28. data/lib/polyphony/extensions/kernel.rb +1 -1
  29. data/lib/polyphony/extensions/object.rb +1 -13
  30. data/lib/polyphony/extensions/process.rb +76 -1
  31. data/lib/polyphony/extensions/socket.rb +0 -14
  32. data/lib/polyphony/extensions/thread.rb +19 -27
  33. data/lib/polyphony/extensions/timeout.rb +5 -1
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +11 -5
  36. data/test/helper.rb +46 -4
  37. data/test/open3/envutil.rb +380 -0
  38. data/test/open3/find_executable.rb +24 -0
  39. data/test/stress.rb +11 -7
  40. data/test/test_backend.rb +11 -4
  41. data/test/test_event.rb +10 -3
  42. data/test/test_ext.rb +16 -1
  43. data/test/test_fiber.rb +16 -4
  44. data/test/test_global_api.rb +17 -16
  45. data/test/test_io.rb +39 -0
  46. data/test/test_kernel.rb +2 -2
  47. data/test/test_monitor.rb +356 -0
  48. data/test/test_open3.rb +338 -0
  49. data/test/test_signal.rb +5 -1
  50. data/test/test_socket.rb +6 -98
  51. data/test/test_sync.rb +46 -0
  52. data/test/test_thread.rb +10 -1
  53. data/test/test_thread_pool.rb +5 -0
  54. data/test/test_throttler.rb +1 -1
  55. data/test/test_timer.rb +8 -2
  56. data/test/test_trace.rb +2 -0
  57. data/vendor/liburing/.github/workflows/build.yml +8 -0
  58. data/vendor/liburing/.gitignore +1 -0
  59. data/vendor/liburing/CHANGELOG +8 -0
  60. data/vendor/liburing/configure +17 -25
  61. data/vendor/liburing/debian/liburing-dev.manpages +2 -0
  62. data/vendor/liburing/debian/rules +2 -1
  63. data/vendor/liburing/examples/Makefile +2 -1
  64. data/vendor/liburing/examples/io_uring-udp.c +11 -3
  65. data/vendor/liburing/examples/rsrc-update-bench.c +100 -0
  66. data/vendor/liburing/liburing.spec +1 -1
  67. data/vendor/liburing/make-debs.sh +4 -2
  68. data/vendor/liburing/src/Makefile +5 -5
  69. data/vendor/liburing/src/arch/aarch64/lib.h +1 -1
  70. data/vendor/liburing/src/include/liburing/io_uring.h +41 -16
  71. data/vendor/liburing/src/include/liburing.h +86 -11
  72. data/vendor/liburing/src/int_flags.h +1 -0
  73. data/vendor/liburing/src/liburing-ffi.map +12 -0
  74. data/vendor/liburing/src/liburing.map +8 -0
  75. data/vendor/liburing/src/register.c +7 -2
  76. data/vendor/liburing/src/setup.c +373 -81
  77. data/vendor/liburing/test/232c93d07b74.c +3 -3
  78. data/vendor/liburing/test/Makefile +10 -3
  79. data/vendor/liburing/test/accept.c +2 -1
  80. data/vendor/liburing/test/buf-ring.c +35 -75
  81. data/vendor/liburing/test/connect-rep.c +204 -0
  82. data/vendor/liburing/test/coredump.c +59 -0
  83. data/vendor/liburing/test/fallocate.c +9 -0
  84. data/vendor/liburing/test/fd-pass.c +34 -3
  85. data/vendor/liburing/test/file-verify.c +27 -6
  86. data/vendor/liburing/test/helpers.c +3 -1
  87. data/vendor/liburing/test/io_uring_register.c +25 -28
  88. data/vendor/liburing/test/io_uring_setup.c +1 -1
  89. data/vendor/liburing/test/poll-cancel-all.c +29 -5
  90. data/vendor/liburing/test/poll-race-mshot.c +6 -22
  91. data/vendor/liburing/test/read-write.c +53 -0
  92. data/vendor/liburing/test/recv-msgall.c +21 -23
  93. data/vendor/liburing/test/reg-fd-only.c +55 -0
  94. data/vendor/liburing/test/reg-hint.c +56 -0
  95. data/vendor/liburing/test/regbuf-merge.c +91 -0
  96. data/vendor/liburing/test/ringbuf-read.c +2 -10
  97. data/vendor/liburing/test/send_recvmsg.c +5 -16
  98. data/vendor/liburing/test/shutdown.c +2 -1
  99. data/vendor/liburing/test/socket-io-cmd.c +215 -0
  100. data/vendor/liburing/test/socket-rw-eagain.c +2 -1
  101. data/vendor/liburing/test/socket-rw-offset.c +2 -1
  102. data/vendor/liburing/test/socket-rw.c +2 -1
  103. data/vendor/liburing/test/timeout.c +276 -0
  104. data/vendor/liburing/test/xattr.c +38 -25
  105. metadata +20 -7
  106. 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
  #