polyphony 0.16 → 0.17

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 (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)