polyphony 0.24 → 0.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +12 -8
  5. data/docs/README.md +2 -2
  6. data/docs/summary.md +3 -3
  7. data/docs/technical-overview/concurrency.md +4 -6
  8. data/docs/technical-overview/design-principles.md +8 -8
  9. data/docs/technical-overview/exception-handling.md +1 -1
  10. data/examples/core/{01-spinning-up-coprocesses.rb → 01-spinning-up-fibers.rb} +1 -1
  11. data/examples/core/{02-awaiting-coprocesses.rb → 02-awaiting-fibers.rb} +3 -3
  12. data/examples/core/xx-erlang-style-genserver.rb +10 -10
  13. data/examples/core/xx-extended_fibers.rb +150 -0
  14. data/examples/core/xx-sleeping.rb +9 -0
  15. data/examples/core/xx-supervisors.rb +1 -1
  16. data/examples/interfaces/pg_pool.rb +3 -3
  17. data/examples/performance/mem-usage.rb +19 -4
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -5
  19. data/ext/gyro/gyro.c +9 -15
  20. data/lib/polyphony/core/cancel_scope.rb +0 -2
  21. data/lib/polyphony/core/exceptions.rb +2 -2
  22. data/lib/polyphony/core/global_api.rb +7 -8
  23. data/lib/polyphony/core/supervisor.rb +25 -31
  24. data/lib/polyphony/extensions/core.rb +4 -78
  25. data/lib/polyphony/extensions/fiber.rb +166 -0
  26. data/lib/polyphony/extensions/io.rb +2 -1
  27. data/lib/polyphony/version.rb +1 -1
  28. data/lib/polyphony.rb +6 -8
  29. data/test/test_async.rb +2 -2
  30. data/test/test_cancel_scope.rb +6 -6
  31. data/test/test_fiber.rb +382 -0
  32. data/test/test_global_api.rb +49 -50
  33. data/test/test_gyro.rb +1 -1
  34. data/test/test_io.rb +30 -29
  35. data/test/test_kernel.rb +2 -2
  36. data/test/test_signal.rb +1 -1
  37. data/test/test_supervisor.rb +27 -27
  38. data/test/test_timer.rb +2 -2
  39. metadata +7 -7
  40. data/examples/core/04-no-auto-run.rb +0 -16
  41. data/lib/polyphony/core/coprocess.rb +0 -168
  42. data/test/test_coprocess.rb +0 -440
data/test/test_io.rb CHANGED
@@ -49,33 +49,33 @@ class IOTest < MiniTest::Test
49
49
 
50
50
  spin { msg = @i.read }
51
51
  ].each(&:await)
52
- assert_equal(5, count)
53
- assert_equal('hello', msg)
52
+ assert_equal 5, count
53
+ assert_equal 'hello', msg
54
54
  end
55
55
 
56
56
  def test_that_double_chevron_method_returns_io
57
- assert_equal(@o, @o << 'foo')
57
+ assert_equal @o, @o << 'foo'
58
58
 
59
59
  @o << 'bar' << 'baz'
60
60
  @o.close
61
- assert_equal('foobarbaz', @i.read)
61
+ assert_equal 'foobarbaz', @i.read
62
62
  end
63
63
  end
64
64
 
65
65
  class IOClassMethodsTest < MiniTest::Test
66
66
  def test_binread
67
67
  s = IO.binread(__FILE__)
68
- assert_kind_of(String, s)
69
- assert(!s.empty?)
70
- assert_equal(IO.orig_binread(__FILE__), s)
68
+ assert_kind_of String, s
69
+ assert !s.empty?
70
+ assert_equal IO.orig_binread(__FILE__), s
71
71
 
72
72
  s = IO.binread(__FILE__, 100)
73
- assert_equal(100, s.bytesize)
74
- assert_equal(IO.orig_binread(__FILE__, 100), s)
73
+ assert_equal 100, s.bytesize
74
+ assert_equal IO.orig_binread(__FILE__, 100), s
75
75
 
76
76
  s = IO.binread(__FILE__, 100, 2)
77
- assert_equal(100, s.bytesize)
78
- assert_equal('frozen', s[0..5])
77
+ assert_equal 100, s.bytesize
78
+ assert_equal 'frozen', s[0..5]
79
79
  end
80
80
 
81
81
  BIN_DATA = "\x00\x01\x02\x03"
@@ -85,37 +85,38 @@ class IOClassMethodsTest < MiniTest::Test
85
85
  FileUtils.rm(fn) rescue nil
86
86
 
87
87
  len = IO.binwrite(fn, BIN_DATA)
88
- assert_equal(4, len)
88
+ assert_equal 4, len
89
89
  s = IO.binread(fn)
90
- assert_equal(BIN_DATA, s)
90
+ assert_equal BIN_DATA, s
91
91
  end
92
92
 
93
93
  def test_foreach
94
+ skip "IO.foreach is not yet implemented"
94
95
  lines = []
95
96
  IO.foreach(__FILE__) { |l| lines << l }
96
- assert_equal("# frozen_string_literal: true\n", lines[0])
97
- assert_equal("end\n", lines[-1])
97
+ assert_equal "# frozen_string_literal: true\n", lines[0]
98
+ assert_equal "end\n", lines[-1]
98
99
  end
99
100
 
100
101
  def test_read
101
102
  s = IO.read(__FILE__)
102
- assert_kind_of(String, s)
103
+ assert_kind_of String, s
103
104
  assert(!s.empty?)
104
- assert_equal(IO.orig_read(__FILE__), s)
105
+ assert_equal IO.orig_read(__FILE__), s
105
106
 
106
107
  s = IO.read(__FILE__, 100)
107
- assert_equal(100, s.bytesize)
108
- assert_equal(IO.orig_read(__FILE__, 100), s)
108
+ assert_equal 100, s.bytesize
109
+ assert_equal IO.orig_read(__FILE__, 100), s
109
110
 
110
111
  s = IO.read(__FILE__, 100, 2)
111
- assert_equal(100, s.bytesize)
112
- assert_equal('frozen', s[0..5])
112
+ assert_equal 100, s.bytesize
113
+ assert_equal 'frozen', s[0..5]
113
114
  end
114
115
 
115
116
  def test_readlines
116
117
  lines = IO.readlines(__FILE__)
117
- assert_equal("# frozen_string_literal: true\n", lines[0])
118
- assert_equal("end\n", lines[-1])
118
+ assert_equal "# frozen_string_literal: true\n", lines[0]
119
+ assert_equal "end\n", lines[-1]
119
120
  end
120
121
 
121
122
  WRITE_DATA = "foo\nbar קוקו"
@@ -125,9 +126,9 @@ class IOClassMethodsTest < MiniTest::Test
125
126
  FileUtils.rm(fn) rescue nil
126
127
 
127
128
  len = IO.write(fn, WRITE_DATA)
128
- assert_equal(WRITE_DATA.bytesize, len)
129
+ assert_equal WRITE_DATA.bytesize, len
129
130
  s = IO.read(fn)
130
- assert_equal(WRITE_DATA, s)
131
+ assert_equal WRITE_DATA, s
131
132
  end
132
133
 
133
134
  def test_popen
@@ -139,7 +140,7 @@ class IOClassMethodsTest < MiniTest::Test
139
140
 
140
141
  result = nil
141
142
  IO.popen('echo "foo"') { |io| result = io.read(8192) }
142
- assert_equal("foo\n", result)
143
+ assert_equal "foo\n", result
143
144
  ensure
144
145
  timer&.stop
145
146
  end
@@ -158,7 +159,7 @@ class IOClassMethodsTest < MiniTest::Test
158
159
  end
159
160
 
160
161
  assert(counter >= 0)
161
- assert_equal("foo\n", gets)
162
+ assert_equal "foo\n", gets
162
163
  ensure
163
164
  $stdin = orig_stdin
164
165
  timer&.stop
@@ -170,7 +171,7 @@ class IOClassMethodsTest < MiniTest::Test
170
171
  s = StringIO.new(IO.orig_read(__FILE__))
171
172
 
172
173
  while (l = s.gets)
173
- assert_equal(l, gets)
174
+ assert_equal l, gets
174
175
  end
175
176
  ensure
176
177
  ARGV.delete __FILE__
@@ -188,7 +189,7 @@ class IOClassMethodsTest < MiniTest::Test
188
189
  $stdout = o
189
190
 
190
191
  puts 'foobar'
191
- assert_equal("foobar\n", o.buf)
192
+ assert_equal "foobar\n", o.buf
192
193
  ensure
193
194
  $stdout = orig_stdout
194
195
  end
data/test/test_kernel.rb CHANGED
@@ -15,7 +15,7 @@ class KernelTest < MiniTest::Test
15
15
  $stdout = o
16
16
  system('echo "hello"')
17
17
  o.close
18
- assert_equal("hello\n", i.read)
18
+ assert_equal "hello\n", i.read
19
19
  ensure
20
20
  $stdout = orig_stdout
21
21
  timer&.stop
@@ -29,7 +29,7 @@ class KernelTest < MiniTest::Test
29
29
  assert(counter >= 2)
30
30
 
31
31
  result = `echo "hello"`
32
- assert_equal("hello\n", result)
32
+ assert_equal "hello\n", result
33
33
  ensure
34
34
  timer&.stop
35
35
  end
data/test/test_signal.rb CHANGED
@@ -20,7 +20,7 @@ class SignalTest < MiniTest::Test
20
20
  Process.kill(:USR1, Process.pid)
21
21
  end
22
22
  suspend
23
- assert_equal(1, count)
23
+ assert_equal 1, count
24
24
  end
25
25
 
26
26
  def test_wait_for_signal_api
@@ -13,7 +13,7 @@ class SupervisorTest < MiniTest::Test
13
13
  assert_equal [:foo], result
14
14
  end
15
15
 
16
- def test_await_multiple_coprocs
16
+ def test_await_multiple_fibers
17
17
  result = Polyphony::Supervisor.new.await { |s|
18
18
  (1..3).each { |i|
19
19
  s.spin {
@@ -25,7 +25,7 @@ class SupervisorTest < MiniTest::Test
25
25
  assert_equal [10, 20, 30], result
26
26
  end
27
27
 
28
- def test_join_multiple_coprocs
28
+ def test_join_multiple_fibers
29
29
  result = Polyphony::Supervisor.new.join { |s|
30
30
  (1..3).each { |i|
31
31
  s.spin {
@@ -48,24 +48,24 @@ class SupervisorTest < MiniTest::Test
48
48
  }
49
49
  }
50
50
 
51
- assert_equal [Polyphony::Coprocess], buffer.map { |v| v.class }.uniq
51
+ assert_equal [Fiber], buffer.map { |v| v.class }.uniq
52
52
  end
53
53
 
54
54
  def test_supervisor_select
55
55
  buffer = []
56
- foo_cp = bar_cp = baz_cp = nil
57
- result, cp = Polyphony::Supervisor.new.select { |s|
58
- foo_cp = s.spin { sleep 0.01; buffer << :foo; :foo }
59
- bar_cp = s.spin { sleep 0.02; buffer << :bar; :bar }
60
- baz_cp = s.spin { sleep 0.03; buffer << :baz; :baz }
56
+ foo_f = bar_f = baz_f = nil
57
+ result, f = Polyphony::Supervisor.new.select { |s|
58
+ foo_f = s.spin { sleep 0.01; buffer << :foo; :foo }
59
+ bar_f = s.spin { sleep 0.02; buffer << :bar; :bar }
60
+ baz_f = s.spin { sleep 0.03; buffer << :baz; :baz }
61
61
  }
62
62
 
63
63
  assert_equal :foo, result
64
- assert_equal foo_cp, cp
64
+ assert_equal foo_f, f
65
65
 
66
66
  sleep 0.03
67
- assert_nil bar_cp.alive?
68
- assert_nil baz_cp.alive?
67
+ assert !bar_f.running?
68
+ assert !baz_f.running?
69
69
  assert_equal [:foo], buffer
70
70
  end
71
71
 
@@ -137,31 +137,31 @@ class SupervisorTest < MiniTest::Test
137
137
  end
138
138
  end
139
139
 
140
- class CoprocessExtensionsTest < MiniTest::Test
140
+ class FiberExtensionsTest < MiniTest::Test
141
141
  def test_join
142
- cp1 = spin { :foo }
143
- cp2 = spin { :bar }
144
- assert_equal [:foo, :bar], Polyphony::Coprocess.join(cp1, cp2)
142
+ f1 = spin { :foo }
143
+ f2 = spin { :bar }
144
+ assert_equal [:foo, :bar], Fiber.join(f1, f2)
145
145
 
146
- cp1 = spin { :foo }
147
- cp2 = spin { raise 'bar' }
148
- result = capture_exception { Polyphony::Coprocess.join(cp1, cp2) }
146
+ f1 = spin { :foo }
147
+ f2 = spin { raise 'bar' }
148
+ result = capture_exception { Fiber.join(f1, f2) }
149
149
  assert_kind_of RuntimeError, result
150
150
  assert_equal 'bar', result.message
151
151
  end
152
152
 
153
153
  def test_select
154
- cp1 = spin { sleep 1; :foo }
155
- cp2 = spin { :bar }
156
- assert_equal [:bar, cp2], Polyphony::Coprocess.select(cp1, cp2)
154
+ f1 = spin { sleep 1; :foo }
155
+ f2 = spin { :bar }
156
+ assert_equal [:bar, f2], Fiber.select(f1, f2)
157
157
 
158
- cp1 = spin { :foo }
159
- cp2 = spin { sleep 0.01; raise 'bar' }
160
- assert_equal [:foo, cp1], Polyphony::Coprocess.select(cp1, cp2)
158
+ f1 = spin { :foo }
159
+ f2 = spin { sleep 0.01; raise 'bar' }
160
+ assert_equal [:foo, f1], Fiber.select(f1, f2)
161
161
 
162
- cp1 = spin { sleep 1; :foo }
163
- cp2 = spin { raise 'bar' }
164
- result = capture_exception { Polyphony::Coprocess.select(cp1, cp2) }
162
+ f1 = spin { sleep 1; :foo }
163
+ f2 = spin { raise 'bar' }
164
+ result = capture_exception { Fiber.select(f1, f2) }
165
165
  assert_kind_of RuntimeError, result
166
166
  assert_equal 'bar', result.message
167
167
  end
data/test/test_timer.rb CHANGED
@@ -11,7 +11,7 @@ class TimerTest < MiniTest::Test
11
11
  count += 1
12
12
  }
13
13
  suspend
14
- assert_equal(1, count)
14
+ assert_equal 1, count
15
15
  end
16
16
 
17
17
  def test_that_repeating_timer_works
@@ -25,7 +25,7 @@ class TimerTest < MiniTest::Test
25
25
  }
26
26
  }
27
27
  suspend
28
- assert_equal(3, count)
28
+ assert_equal 3, count
29
29
  end
30
30
 
31
31
  def test_that_repeating_timer_compensates_for_drift
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.24'
4
+ version: '0.25'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-08 00:00:00.000000000 Z
11
+ date: 2020-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -194,13 +194,13 @@ files:
194
194
  - docs/technical-overview/faq.md
195
195
  - docs/technical-overview/fiber-scheduling.md
196
196
  - docs/user-guide/web-server.md
197
- - examples/core/01-spinning-up-coprocesses.rb
198
- - examples/core/02-awaiting-coprocesses.rb
197
+ - examples/core/01-spinning-up-fibers.rb
198
+ - examples/core/02-awaiting-fibers.rb
199
199
  - examples/core/03-interrupting.rb
200
- - examples/core/04-no-auto-run.rb
201
200
  - examples/core/xx-channels.rb
202
201
  - examples/core/xx-deferring-an-operation.rb
203
202
  - examples/core/xx-erlang-style-genserver.rb
203
+ - examples/core/xx-extended_fibers.rb
204
204
  - examples/core/xx-forking.rb
205
205
  - examples/core/xx-move_on.rb
206
206
  - examples/core/xx-recurrent-timer.rb
@@ -277,7 +277,6 @@ files:
277
277
  - lib/polyphony.rb
278
278
  - lib/polyphony/core/cancel_scope.rb
279
279
  - lib/polyphony/core/channel.rb
280
- - lib/polyphony/core/coprocess.rb
281
280
  - lib/polyphony/core/exceptions.rb
282
281
  - lib/polyphony/core/global_api.rb
283
282
  - lib/polyphony/core/resource_pool.rb
@@ -287,6 +286,7 @@ files:
287
286
  - lib/polyphony/core/thread_pool.rb
288
287
  - lib/polyphony/core/throttler.rb
289
288
  - lib/polyphony/extensions/core.rb
289
+ - lib/polyphony/extensions/fiber.rb
290
290
  - lib/polyphony/extensions/io.rb
291
291
  - lib/polyphony/extensions/openssl.rb
292
292
  - lib/polyphony/extensions/socket.rb
@@ -303,7 +303,7 @@ files:
303
303
  - test/run.rb
304
304
  - test/test_async.rb
305
305
  - test/test_cancel_scope.rb
306
- - test/test_coprocess.rb
306
+ - test/test_fiber.rb
307
307
  - test/test_global_api.rb
308
308
  - test/test_gyro.rb
309
309
  - test/test_io.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
-
5
- require 'polyphony'
6
-
7
- def nap(tag, t)
8
- puts "#{Time.now} #{tag} napping for #{t} seconds..."
9
- sleep t
10
- puts "#{Time.now} #{tag} done napping"
11
- end
12
-
13
- spin { nap(:a, 1) }
14
-
15
- # Wait for any coprocess still alive
16
- suspend
@@ -1,168 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :Coprocess
4
-
5
- import '../extensions/core'
6
- Exceptions = import './exceptions'
7
-
8
- # Encapsulates an asynchronous task
9
- class Coprocess
10
- # inter-coprocess message passing
11
- module Messaging
12
- def <<(value)
13
- if @receive_waiting && @fiber
14
- @fiber&.schedule value
15
- else
16
- @queued_messages ||= []
17
- @queued_messages << value
18
- end
19
- snooze
20
- end
21
-
22
- def receive
23
- if !@queued_messages || @queued_messages&.empty?
24
- wait_for_message
25
- else
26
- value = @queued_messages.shift
27
- snooze
28
- value
29
- end
30
- end
31
-
32
- def wait_for_message
33
- Gyro.ref
34
- @receive_waiting = true
35
- suspend
36
- ensure
37
- Gyro.unref
38
- @receive_waiting = nil
39
- end
40
- end
41
-
42
- include Messaging
43
-
44
- @@map = {}
45
-
46
- def self.map
47
- @@map
48
- end
49
-
50
- def self.count
51
- @@map.size
52
- end
53
-
54
- attr_reader :result, :fiber
55
-
56
- def initialize(fiber = nil, &block)
57
- @fiber = fiber
58
- @block = block
59
- end
60
-
61
- def location
62
- @block ? @block.source_location.join(':') : nil
63
- end
64
-
65
- def caller
66
- @fiber ? @fiber.caller[2..-1] : nil
67
- end
68
-
69
- def run
70
- @calling_fiber = Fiber.current
71
-
72
- @fiber = Fiber.new(location) { |v| execute(v) }
73
- @fiber.schedule
74
- @ran = true
75
- self
76
- end
77
-
78
- def execute(first_value)
79
- # The first value passed to the coprocess can be used to stop it before it
80
- # is scheduled for the first time
81
- raise first_value if first_value.is_a?(Exception)
82
-
83
- @@map[@fiber] = @fiber.coprocess = self
84
- @result = @block.call(self)
85
- rescue Exceptions::MoveOn => e
86
- @result = e.value
87
- rescue Exception => e
88
- uncaught_exception = true
89
- @result = e
90
- ensure
91
- finish_execution(uncaught_exception)
92
- end
93
-
94
- def finish_execution(uncaught_exception)
95
- @@map.delete(@fiber)
96
- @fiber.coprocess = nil
97
- @fiber = nil
98
- @awaiting_fiber&.schedule @result
99
- @when_done&.()
100
-
101
- return unless uncaught_exception && !@awaiting_fiber
102
-
103
- # if no awaiting fiber, raise any uncaught error by passing it to the
104
- # calling fiber, or to the root fiber if the calling fiber
105
- calling_fiber_alive = @calling_fiber && @calling_fiber.state != :dead
106
- calling_fiber = calling_fiber_alive ? @calling_fiber : Fiber.root
107
- calling_fiber.transfer @result
108
- end
109
-
110
- def alive?
111
- @fiber
112
- end
113
-
114
- # Kernel.await expects the given argument / block to be a callable, so #call
115
- # in fact waits for the coprocess to finish
116
- def await
117
- await_coprocess_result
118
- ensure
119
- # If the awaiting fiber has been transferred an exception, the awaited fiber
120
- # might still be running, so we need to stop it
121
- @fiber&.schedule(Exceptions::MoveOn.new)
122
- end
123
- alias_method :join, :await
124
-
125
- def await_coprocess_result
126
- run unless @ran
127
- if @fiber
128
- @awaiting_fiber = Fiber.current
129
- suspend
130
- else
131
- @result
132
- end
133
- end
134
-
135
- def when_done(&block)
136
- @when_done = block
137
- end
138
-
139
- def schedule(value = nil)
140
- @fiber&.schedule(value)
141
- end
142
-
143
- def resume(value = nil)
144
- return unless @fiber
145
-
146
- @fiber.schedule(value)
147
- snooze
148
- end
149
-
150
- def interrupt(value = nil)
151
- return unless @fiber
152
-
153
- @fiber.schedule(Exceptions::MoveOn.new(nil, value))
154
- snooze
155
- end
156
- alias_method :stop, :interrupt
157
-
158
- def cancel!
159
- return unless @fiber
160
-
161
- @fiber.schedule(Exceptions::Cancel.new)
162
- snooze
163
- end
164
-
165
- def self.current
166
- Fiber.current.coprocess
167
- end
168
- end