polyphony 0.24 → 0.25

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