polyphony 0.16 → 0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +11 -11
  5. data/TODO.md +14 -5
  6. data/examples/core/channel_echo.rb +3 -3
  7. data/examples/core/enumerator.rb +1 -1
  8. data/examples/core/fork.rb +1 -1
  9. data/examples/core/genserver.rb +1 -1
  10. data/examples/core/lock.rb +3 -3
  11. data/examples/core/multiple_spawn.rb +2 -2
  12. data/examples/core/nested_async.rb +1 -1
  13. data/examples/core/nested_multiple_spawn.rb +3 -3
  14. data/examples/core/resource.rb +1 -1
  15. data/examples/core/resource_cancel.rb +1 -1
  16. data/examples/core/resource_delegate.rb +1 -1
  17. data/examples/core/sleep_spawn.rb +2 -2
  18. data/examples/core/spawn.rb +1 -1
  19. data/examples/core/spawn_cancel.rb +1 -1
  20. data/examples/core/spawn_error.rb +5 -5
  21. data/examples/core/supervisor.rb +4 -4
  22. data/examples/core/supervisor_with_cancel_scope.rb +3 -3
  23. data/examples/core/supervisor_with_error.rb +4 -4
  24. data/examples/core/supervisor_with_manual_move_on.rb +4 -4
  25. data/examples/core/thread.rb +2 -2
  26. data/examples/core/thread_cancel.rb +2 -2
  27. data/examples/core/thread_pool.rb +2 -2
  28. data/examples/core/throttle.rb +3 -3
  29. data/examples/fs/read.rb +1 -1
  30. data/examples/http/happy_eyeballs.rb +1 -1
  31. data/examples/http/http_client.rb +1 -1
  32. data/examples/http/http_server.rb +1 -1
  33. data/examples/http/http_server_throttled.rb +1 -1
  34. data/examples/http/http_ws_server.rb +2 -2
  35. data/examples/http/https_wss_server.rb +1 -1
  36. data/examples/interfaces/pg_client.rb +1 -1
  37. data/examples/interfaces/pg_pool.rb +1 -1
  38. data/examples/interfaces/redis_channels.rb +5 -5
  39. data/examples/interfaces/redis_pubsub.rb +2 -2
  40. data/examples/interfaces/redis_pubsub_perf.rb +3 -3
  41. data/examples/io/cat.rb +13 -0
  42. data/examples/io/echo_client.rb +2 -2
  43. data/examples/io/echo_server.rb +1 -1
  44. data/examples/io/echo_server_with_timeout.rb +1 -1
  45. data/examples/io/echo_stdin.rb +1 -1
  46. data/examples/io/io_read.rb +9 -0
  47. data/examples/io/system.rb +11 -0
  48. data/examples/performance/perf_multi_snooze.rb +2 -2
  49. data/examples/performance/perf_snooze.rb +2 -2
  50. data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -2
  51. data/ext/ev/io.c +53 -4
  52. data/lib/polyphony/core/coprocess.rb +1 -0
  53. data/lib/polyphony/core/supervisor.rb +1 -1
  54. data/lib/polyphony/extensions/io.rb +97 -17
  55. data/lib/polyphony/extensions/kernel.rb +47 -27
  56. data/lib/polyphony/http/server.rb +1 -1
  57. data/lib/polyphony/postgres.rb +0 -4
  58. data/lib/polyphony/version.rb +1 -1
  59. data/test/test_coprocess.rb +13 -13
  60. data/test/test_core.rb +12 -12
  61. data/test/test_io.rb +95 -3
  62. data/test/test_kernel.rb +26 -0
  63. metadata +6 -2
@@ -82,6 +82,7 @@ class Coprocess
82
82
  suspend
83
83
  end
84
84
  end
85
+ alias_method :join, :await
85
86
 
86
87
  def when_done(&block)
87
88
  @when_done = block
@@ -41,7 +41,7 @@ class Supervisor
41
41
  end
42
42
 
43
43
  def spawn_proc(proc)
44
- @coprocesses << Object.spawn do |coprocess|
44
+ @coprocesses << coproc do |coprocess|
45
45
  proc.call(coprocess)
46
46
  task_completed(coprocess)
47
47
  rescue Exception => e
@@ -1,17 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ::IO
4
- def read_watcher
5
- @read_watcher ||= EV::IO.new(self, :r)
6
- end
7
-
8
- def write_watcher
9
- @write_watcher ||= EV::IO.new(self, :w)
10
- end
11
-
12
- def stop_watchers
13
- @read_watcher&.stop
14
- @write_watcher&.stop
4
+ class << self
5
+ alias_method :orig_binread, :binread
6
+ def binread(name, length = nil, offset = nil)
7
+ File.open(name, 'rb:ASCII-8BIT') do |f|
8
+ f.seek(offset) if offset
9
+ length ? f.read(length) : f.read
10
+ end
11
+ end
12
+
13
+ alias_method :orig_binwrite, :binwrite
14
+ def binwrite(name, string, offset = nil)
15
+ File.open(name, 'wb:ASCII-8BIT') do |f|
16
+ f.seek(offset) if offset
17
+ f.write(string)
18
+ end
19
+ end
20
+
21
+ EMPTY_HASH = {}
22
+
23
+ alias_method :orig_foreach, :foreach
24
+ def foreach(name, sep = $/, limit = nil, getline_args = EMPTY_HASH, &block)
25
+ sep, limit = $/, sep if sep.is_a?(Integer)
26
+ File.open(name, 'r') do |f|
27
+ f.each_line(sep, limit, getline_args, &block)
28
+ end
29
+ end
30
+
31
+ alias_method :orig_read, :read
32
+ def read(name, length = nil, offset = nil, opt = EMPTY_HASH)
33
+ File.open(name, opt[:mode] || 'r') do |f|
34
+ f.seek(offset) if offset
35
+ length ? f.read(length) : f.read
36
+ end
37
+ end
38
+
39
+ alias_method :orig_readlines, :readlines
40
+ def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
41
+ File.open(name, 'r') do |f|
42
+ f.readlines(sep, limit, getline_args)
43
+ end
44
+ end
45
+
46
+ alias_method :orig_write, :write
47
+ def write(name, string, offset = nil, opt = EMPTY_HASH)
48
+ File.open(name, opt[:mode] || 'w') do |f|
49
+ f.seek(offset) if offset
50
+ f.write(string)
51
+ end
52
+ end
53
+
54
+ alias_method :orig_popen, :popen
55
+ def popen(*args)
56
+ Open3.popen2(*args) do |i, o, t|
57
+ yield o
58
+ end
59
+ end
15
60
  end
16
61
 
17
62
  # def each(sep = $/, limit = nil, chomp: nil)
@@ -34,9 +79,28 @@ class ::IO
34
79
  # def getc
35
80
  # end
36
81
 
37
- # def gets(sep = $/, limit = nil, chomp: nil)
38
- # sep, limit = $/, sep if sep.is_a?(Integer)
39
- # end
82
+ alias_method :orig_gets, :gets
83
+ def gets(sep = $/, limit = nil, chomp: nil)
84
+ sep, limit = $/, sep if sep.is_a?(Integer)
85
+ sep_size = sep.bytesize
86
+
87
+ @gets_buffer ||= +''
88
+
89
+ loop do
90
+ idx = @gets_buffer.index(sep)
91
+ return @gets_buffer.slice!(0, idx + sep_size) if idx
92
+
93
+ data = readpartial(8192)
94
+ if data
95
+ @gets_buffer << data
96
+ else
97
+ return nil if @gets_buffer.empty?
98
+ line, @gets_buffer = @gets_buffer.freeze, +''
99
+ return line
100
+ end
101
+ end
102
+ # orig_gets(sep, limit, chomp: chomp)
103
+ end
40
104
 
41
105
  # def print(*args)
42
106
  # end
@@ -47,8 +111,24 @@ class ::IO
47
111
  # def putc(obj)
48
112
  # end
49
113
 
50
- # def puts(*args)
51
- # end
114
+ alias_method :orig_puts, :puts
115
+ def puts(*args)
116
+ if args.empty?
117
+ write "\n"
118
+ return
119
+ end
120
+
121
+ s = args.each_with_object(+'') do |a, str|
122
+ if a.is_a?(Array)
123
+ a.each { |a2| str << a2.to_s << "\n" }
124
+ else
125
+ a = a.to_s
126
+ str << a
127
+ str << "\n" unless a =~ /\n$/
128
+ end
129
+ end
130
+ write s
131
+ end
52
132
 
53
133
  # def readbyte
54
134
  # end
@@ -61,4 +141,4 @@ class ::IO
61
141
 
62
142
  # def readlines(sep = $/, limit = nil, chomp: nil)
63
143
  # end
64
- end
144
+ end
@@ -35,6 +35,35 @@ class ::Exception
35
35
  end
36
36
  end
37
37
 
38
+ class Pulser
39
+ def initialize(freq)
40
+ fiber = Fiber.current
41
+ @timer = EV::Timer.new(freq, freq)
42
+ @timer.start { fiber.transfer freq }
43
+ end
44
+
45
+ def await
46
+ suspend
47
+ rescue Exception => e
48
+ @timer.stop
49
+ raise e
50
+ end
51
+
52
+ def stop
53
+ @timer.stop
54
+ end
55
+ end
56
+
57
+ module ::Process
58
+ def self.detach(pid)
59
+ coproc {
60
+ EV::Child.new(pid).await
61
+ }
62
+ end
63
+ end
64
+
65
+ require 'open3'
66
+
38
67
  # Kernel extensions (methods available to all objects)
39
68
  module ::Kernel
40
69
  def after(duration, &block)
@@ -64,6 +93,14 @@ module ::Kernel
64
93
  CancelScope.new(timeout: duration, mode: :cancel).(&block)
65
94
  end
66
95
 
96
+ def coproc(proc = nil, &block)
97
+ if proc.is_a?(Coprocess)
98
+ proc.run
99
+ else
100
+ Coprocess.new(&(block || proc)).run
101
+ end
102
+ end
103
+
67
104
  def every(freq, &block)
68
105
  EV::Timer.new(freq, freq).start(&block)
69
106
  end
@@ -72,25 +109,6 @@ module ::Kernel
72
109
  CancelScope.new(timeout: duration).(&block)
73
110
  end
74
111
 
75
- class Pulser
76
- def initialize(freq)
77
- fiber = Fiber.current
78
- @timer = EV::Timer.new(freq, freq)
79
- @timer.start { fiber.transfer freq }
80
- end
81
-
82
- def await
83
- suspend
84
- rescue Exception => e
85
- @timer.stop
86
- raise e
87
- end
88
-
89
- def stop
90
- @timer.stop
91
- end
92
- end
93
-
94
112
  def pulse(freq)
95
113
  Pulser.new(freq)
96
114
  end
@@ -107,18 +125,20 @@ module ::Kernel
107
125
  timer.stop
108
126
  end
109
127
 
110
- def spawn(proc = nil, &block)
111
- if proc.is_a?(Coprocess)
112
- proc.run
113
- else
114
- Coprocess.new(&(block || proc)).run
115
- end
116
- end
117
-
118
128
  def supervise(&block)
119
129
  Supervisor.new.await(&block)
120
130
  end
121
131
 
132
+ alias_method :orig_system, :system
133
+ def system(*args)
134
+ Open3.popen2(*args) do |i, o, t|
135
+ i.close
136
+ o.read
137
+ end
138
+ rescue SystemCallError => e
139
+ nil
140
+ end
141
+
122
142
  def throttled_loop(rate, &block)
123
143
  throttler = Throttler.new(rate)
124
144
  loop do
@@ -23,7 +23,7 @@ end
23
23
  def accept_loop(server, opts, &handler)
24
24
  while true
25
25
  client = server.accept
26
- spawn { client_task(client, opts, &handler) }
26
+ coproc { client_task(client, opts, &handler) }
27
27
  end
28
28
  rescue OpenSSL::SSL::SSLError
29
29
  retry # disregard
@@ -20,8 +20,6 @@ module ::PG
20
20
  return
21
21
  end
22
22
  end
23
- ensure
24
- conn.socket_io.stop_watchers
25
23
  end
26
24
 
27
25
  def self.connect_sync(conn)
@@ -46,8 +44,6 @@ class ::PG::Connection
46
44
  consume_input
47
45
  end
48
46
  orig_get_result(&block)
49
- ensure
50
- socket_io.stop_watchers
51
47
  end
52
48
 
53
49
  alias_method :orig_async_exec, :async_exec
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.16'
4
+ VERSION = '0.17'
5
5
  end
@@ -86,7 +86,7 @@ class CoprocessTest < MiniTest::Test
86
86
 
87
87
  def test_that_coprocess_can_be_awaited
88
88
  result = nil
89
- spawn do
89
+ coproc do
90
90
  coprocess = Polyphony::Coprocess.new { sleep(0.001); 42 }
91
91
  result = coprocess.await
92
92
  end
@@ -96,7 +96,7 @@ class CoprocessTest < MiniTest::Test
96
96
 
97
97
  def test_that_coprocess_can_be_stopped
98
98
  result = nil
99
- coprocess = spawn do
99
+ coprocess = coproc do
100
100
  sleep(0.001)
101
101
  result = 42
102
102
  end
@@ -107,7 +107,7 @@ class CoprocessTest < MiniTest::Test
107
107
 
108
108
  def test_that_coprocess_can_be_cancelled
109
109
  result = nil
110
- coprocess = spawn do
110
+ coprocess = coproc do
111
111
  sleep(0.001)
112
112
  result = 42
113
113
  rescue Polyphony::Cancel => e
@@ -125,8 +125,8 @@ class CoprocessTest < MiniTest::Test
125
125
  def test_that_inner_coprocess_can_be_interrupted
126
126
  result = nil
127
127
  coprocess2 = nil
128
- coprocess = spawn do
129
- coprocess2 = spawn do
128
+ coprocess = coproc do
129
+ coprocess2 = coproc do
130
130
  sleep(0.001)
131
131
  result = 42
132
132
  end
@@ -143,8 +143,8 @@ class CoprocessTest < MiniTest::Test
143
143
  def test_that_inner_coprocess_can_interrupt_outer_coprocess
144
144
  result, coprocess2 = nil
145
145
 
146
- coprocess = spawn do
147
- coprocess2 = spawn do
146
+ coprocess = coproc do
147
+ coprocess2 = coproc do
148
148
  EV.next_tick { coprocess.interrupt }
149
149
  sleep(0.001)
150
150
  result = 42
@@ -168,7 +168,7 @@ class MailboxTest < MiniTest::Test
168
168
 
169
169
  def test_that_coprocess_can_receive_messages
170
170
  msgs = []
171
- coproc = spawn {
171
+ coprocess = coproc {
172
172
  loop {
173
173
  msgs << receive
174
174
  }
@@ -176,16 +176,16 @@ class MailboxTest < MiniTest::Test
176
176
 
177
177
  EV.snooze # allow coproc to start
178
178
 
179
- 3.times { |i| coproc << i; EV.snooze }
179
+ 3.times { |i| coprocess << i; EV.snooze }
180
180
 
181
181
  assert_equal([0, 1, 2], msgs)
182
182
  ensure
183
- coproc.stop
183
+ coprocess.stop
184
184
  end
185
185
 
186
186
  def test_that_multiple_messages_sent_at_once_arrive
187
187
  msgs = []
188
- coproc = spawn {
188
+ coprocess = coproc {
189
189
  loop {
190
190
  msgs << receive
191
191
  }
@@ -193,12 +193,12 @@ class MailboxTest < MiniTest::Test
193
193
 
194
194
  EV.snooze # allow coproc to start
195
195
 
196
- 3.times { |i| coproc << i }
196
+ 3.times { |i| coprocess << i }
197
197
 
198
198
  EV.snooze
199
199
 
200
200
  assert_equal([0, 1, 2], msgs)
201
201
  ensure
202
- coproc.stop
202
+ coprocess.stop
203
203
  end
204
204
  end
data/test/test_core.rb CHANGED
@@ -9,7 +9,7 @@ class SpawnTest < MiniTest::Test
9
9
 
10
10
  def test_that_spawn_returns_a_coprocess
11
11
  result = nil
12
- coprocess = spawn { result = 42 }
12
+ coprocess = coproc { result = 42 }
13
13
 
14
14
  assert_kind_of(Polyphony::Coprocess, coprocess)
15
15
  assert_nil(result)
@@ -20,7 +20,7 @@ class SpawnTest < MiniTest::Test
20
20
  def test_that_spawn_accepts_coprocess_argument
21
21
  result = nil
22
22
  coprocess = Polyphony::Coprocess.new { result = 42 }
23
- spawn coprocess
23
+ coproc coprocess
24
24
 
25
25
  assert_nil(result)
26
26
  suspend
@@ -28,7 +28,7 @@ class SpawnTest < MiniTest::Test
28
28
  end
29
29
 
30
30
  def test_that_spawned_coprocess_saves_result
31
- coprocess = spawn { 42 }
31
+ coprocess = coproc { 42 }
32
32
 
33
33
  assert_kind_of(Polyphony::Coprocess, coprocess)
34
34
  assert_nil(coprocess.result)
@@ -38,7 +38,7 @@ class SpawnTest < MiniTest::Test
38
38
 
39
39
  def test_that_spawned_coprocess_can_be_interrupted
40
40
  result = nil
41
- coprocess = spawn { sleep(1); 42 }
41
+ coprocess = coproc { sleep(1); 42 }
42
42
  EV.next_tick { coprocess.interrupt }
43
43
  suspend
44
44
  assert_nil(coprocess.result)
@@ -59,7 +59,7 @@ class CancelScopeTest < Minitest::Test
59
59
 
60
60
  def test_that_cancel_scope_cancels_coprocess
61
61
  ctx = {}
62
- spawn do
62
+ coproc do
63
63
  EV::Timer.new(0.005, 0).start { ctx[:cancel_scope]&.cancel! }
64
64
  sleep_with_cancel(ctx, :cancel)
65
65
  rescue Exception => e
@@ -76,7 +76,7 @@ class CancelScopeTest < Minitest::Test
76
76
 
77
77
  # def test_that_cancel_scope_cancels_async_op_with_stop
78
78
  # ctx = {}
79
- # spawn do
79
+ # coproc do
80
80
  # EV::Timer.new(0, 0).start { ctx[:cancel_scope].cancel! }
81
81
  # sleep_with_cancel(ctx, :stop)
82
82
  # end
@@ -88,7 +88,7 @@ class CancelScopeTest < Minitest::Test
88
88
 
89
89
  def test_that_cancel_after_raises_cancelled_exception
90
90
  result = nil
91
- spawn do
91
+ coproc do
92
92
  cancel_after(0.01) do
93
93
  sleep(1000)
94
94
  end
@@ -103,7 +103,7 @@ class CancelScopeTest < Minitest::Test
103
103
  def test_that_cancel_scopes_can_be_nested
104
104
  inner_result = nil
105
105
  outer_result = nil
106
- spawn do
106
+ coproc do
107
107
  move_on_after(0.01) do
108
108
  move_on_after(0.02) do
109
109
  sleep(1000)
@@ -119,7 +119,7 @@ class CancelScopeTest < Minitest::Test
119
119
  EV.rerun
120
120
 
121
121
  outer_result = nil
122
- spawn do
122
+ coproc do
123
123
  move_on_after(0.02) do
124
124
  move_on_after(0.01) do
125
125
  sleep(1000)
@@ -154,7 +154,7 @@ class SupervisorTest < MiniTest::Test
154
154
 
155
155
  def test_that_supervisor_waits_for_all_nested_coprocesses_to_complete
156
156
  ctx = {}
157
- spawn do
157
+ coproc do
158
158
  parallel_sleep(ctx)
159
159
  end
160
160
  suspend
@@ -165,10 +165,10 @@ class SupervisorTest < MiniTest::Test
165
165
 
166
166
  def test_that_supervisor_can_add_coprocesses_after_having_started
167
167
  result = []
168
- spawn {
168
+ coproc {
169
169
  supervisor = Polyphony::Supervisor.new
170
170
  3.times do |i|
171
- spawn do
171
+ coproc do
172
172
  sleep(0.001)
173
173
  supervisor.spawn do
174
174
  sleep(0.001)