polyphony 0.29 → 0.30

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +15 -10
  6. data/docs/getting-started/tutorial.md +3 -3
  7. data/docs/index.md +2 -3
  8. data/docs/{technical-overview → main-concepts}/concurrency.md +62 -15
  9. data/docs/{technical-overview → main-concepts}/design-principles.md +21 -8
  10. data/docs/{technical-overview → main-concepts}/exception-handling.md +80 -38
  11. data/docs/{technical-overview → main-concepts}/extending.md +4 -3
  12. data/docs/{technical-overview → main-concepts}/fiber-scheduling.md +3 -3
  13. data/docs/{technical-overview.md → main-concepts.md} +2 -2
  14. data/examples/core/xx-at_exit.rb +29 -0
  15. data/examples/core/xx-fork-terminate.rb +27 -0
  16. data/examples/core/xx-pingpong.rb +18 -0
  17. data/examples/core/xx-stop.rb +20 -0
  18. data/ext/gyro/async.c +1 -1
  19. data/ext/gyro/extconf.rb +0 -3
  20. data/ext/gyro/gyro.c +7 -8
  21. data/ext/gyro/gyro.h +2 -0
  22. data/ext/gyro/queue.c +6 -6
  23. data/ext/gyro/selector.c +32 -1
  24. data/ext/gyro/thread.c +55 -9
  25. data/ext/gyro/timer.c +1 -0
  26. data/lib/polyphony/core/exceptions.rb +4 -1
  27. data/lib/polyphony/core/global_api.rb +1 -6
  28. data/lib/polyphony/core/thread_pool.rb +3 -3
  29. data/lib/polyphony/extensions/core.rb +7 -1
  30. data/lib/polyphony/extensions/fiber.rb +159 -72
  31. data/lib/polyphony/extensions/io.rb +2 -4
  32. data/lib/polyphony/extensions/openssl.rb +0 -17
  33. data/lib/polyphony/extensions/thread.rb +46 -22
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +20 -18
  36. data/test/coverage.rb +1 -1
  37. data/test/helper.rb +7 -3
  38. data/test/test_fiber.rb +285 -72
  39. data/test/test_global_api.rb +7 -52
  40. data/test/test_io.rb +8 -0
  41. data/test/test_signal.rb +1 -0
  42. data/test/test_thread.rb +76 -56
  43. data/test/test_thread_pool.rb +27 -5
  44. data/test/test_throttler.rb +1 -0
  45. metadata +12 -12
  46. data/lib/polyphony/core/supervisor.rb +0 -114
  47. data/lib/polyphony/line_reader.rb +0 -82
  48. data/test/test_gyro.rb +0 -25
  49. data/test/test_supervisor.rb +0 -180
data/test/test_thread.rb CHANGED
@@ -9,7 +9,7 @@ class ThreadTest < MiniTest::Test
9
9
  t = Thread.new do
10
10
  s1 = spin { (11..13).each { |i| snooze; buffer << i } }
11
11
  s2 = spin { (21..23).each { |i| snooze; buffer << i } }
12
- Fiber.join(s1, s2)
12
+ Fiber.current.await_all_children
13
13
  end
14
14
  f.join
15
15
  t.join
@@ -18,22 +18,16 @@ class ThreadTest < MiniTest::Test
18
18
  end
19
19
 
20
20
  def test_thread_join
21
- tr = nil
22
- # tr = Polyphony::Trace.new(:fiber_all) { |r| p r[:event] }
23
- # Gyro.trace(true)
24
- # tr.enable
25
-
26
21
  buffer = []
27
22
  spin { (1..3).each { |i| snooze; buffer << i } }
28
- t = Thread.new { sleep 0.01; buffer << 4 }
23
+ t = Thread.new { sleep 0.01; buffer << 4; :foo }
29
24
 
30
25
  r = t.join
31
26
 
32
27
  assert_equal [1, 2, 3, 4], buffer
33
- assert_equal t, r
28
+ assert_equal :foo, r
34
29
  ensure
35
- tr&.disable
36
- Gyro.trace(nil)
30
+ t.kill
37
31
  end
38
32
 
39
33
  def test_thread_join_with_timeout
@@ -49,12 +43,54 @@ class ThreadTest < MiniTest::Test
49
43
  ensure
50
44
  # killing the thread will prevent stopping the sleep timer, as well as the
51
45
  # thread's event selector, leading to a memory leak.
52
- t.kill if t.alive?
46
+ t&.kill if t&.alive?
47
+ end
48
+
49
+ def test_thread_await_alias_method
50
+ buffer = []
51
+ spin { (1..3).each { |i| snooze; buffer << i } }
52
+ t = Thread.new { sleep 0.01; buffer << 4; :foo }
53
+
54
+ r = t.await
55
+
56
+ assert_equal [1, 2, 3, 4], buffer
57
+ assert_equal :foo, r
58
+ ensure
59
+ t.kill
60
+ end
61
+
62
+ def test_join_race_condition_on_thread_spawning
63
+ buffer = []
64
+ t = Thread.new do
65
+ :foo
66
+ end
67
+ r = t.join
68
+ assert_equal :foo, r
69
+ end
70
+
71
+ def test_thread_uncaught_exception_propagation
72
+ t = Thread.new do
73
+ sleep 1
74
+ end
75
+ snooze
76
+ t.kill
77
+ t.await
78
+
79
+ t = Thread.new do
80
+ raise 'foo'
81
+ end
82
+ e = nil
83
+ begin
84
+ t.await
85
+ rescue Exception => e
86
+ end
87
+ assert_kind_of RuntimeError, e
88
+ assert_equal 'foo', e.message
53
89
  end
54
90
 
55
91
  def test_thread_inspect
56
92
  lineno = __LINE__ + 1
57
- t = Thread.new {}
93
+ t = Thread.new { sleep 1 }
58
94
  str = format(
59
95
  "#<Thread:%d %s:%d (run)>",
60
96
  t.object_id,
@@ -62,11 +98,18 @@ class ThreadTest < MiniTest::Test
62
98
  lineno,
63
99
  )
64
100
  assert_equal str, t.inspect
101
+ rescue => e
102
+ p e
103
+ ensure
104
+ t.kill
105
+ t.join
65
106
  end
66
107
 
67
108
  def test_that_suspend_returns_immediately_if_no_watchers
68
109
  records = []
69
- t = Polyphony::Trace.new(:fiber_all) { |r| records << r if r[:event] =~ /^fiber_/ }
110
+ t = Polyphony::Trace.new(:fiber_all) do |r|
111
+ records << r if r[:event] =~ /^fiber_/
112
+ end
70
113
  t.enable
71
114
  Gyro.trace(true)
72
115
 
@@ -78,50 +121,27 @@ class ThreadTest < MiniTest::Test
78
121
  Gyro.trace(false)
79
122
  end
80
123
 
81
- def test_reset
82
- values = []
83
- f1 = spin do
84
- values << :foo
85
- snooze
86
- values << :bar
87
- suspend
88
- end
89
-
90
- f2 = spin do
91
- Thread.current.reset_fiber_scheduling
92
- values << :restarted
93
- snooze
94
- values << :baz
95
- end
96
-
97
- suspend
98
-
99
- f1.schedule
100
- suspend
101
- assert_equal %i[foo restarted baz], values
102
- end
103
-
104
- def test_restart
105
- values = []
106
- spin do
107
- values << :foo
108
- snooze
109
- # this part will not be reached, as Gyro state is reset
110
- values << :bar
111
- suspend
112
- end
113
-
114
- spin do
115
- Thread.current.reset_fiber_scheduling
116
-
117
- # control is transfer to the fiber that called Gyro.restart
118
- values << :restarted
119
- snooze
120
- values << :baz
124
+ def test_thread_child_fiber_termination
125
+ buffer = []
126
+ t = Thread.new do
127
+ spin do
128
+ sleep 61
129
+ ensure
130
+ buffer << :foo
131
+ end
132
+ spin do
133
+ sleep 62
134
+ ensure
135
+ buffer << :bar
136
+ end
137
+ assert 2, Fiber.current.children.size
138
+ sleep 1
121
139
  end
140
+ sleep 0.05
141
+ assert_equal 2, t.main_fiber.children.size
142
+ t.kill
143
+ t.join
122
144
 
123
- suspend
124
-
125
- assert_equal %i[foo restarted baz], values
145
+ assert_equal [:foo, :bar], buffer
126
146
  end
127
147
  end
@@ -5,11 +5,10 @@ require_relative 'helper'
5
5
  class ThreadPoolTest < MiniTest::Test
6
6
  def setup
7
7
  super
8
- # @pool = Polyphony::ThreadPool.new
8
+ @pool = Polyphony::ThreadPool.new
9
9
  end
10
10
 
11
11
  def test_process
12
- skip
13
12
  current_thread = Thread.current
14
13
 
15
14
  processing_thread = nil
@@ -22,7 +21,6 @@ class ThreadPoolTest < MiniTest::Test
22
21
  end
23
22
 
24
23
  def test_multi_process
25
- skip
26
24
  current_thread = Thread.current
27
25
  threads = []
28
26
  results = []
@@ -44,7 +42,6 @@ class ThreadPoolTest < MiniTest::Test
44
42
  end
45
43
 
46
44
  def test_process_with_exception
47
- skip
48
45
  result = nil
49
46
  begin
50
47
  result = @pool.process { raise 'foo' }
@@ -56,7 +53,6 @@ class ThreadPoolTest < MiniTest::Test
56
53
  end
57
54
 
58
55
  def test_cast
59
- skip
60
56
  t0 = Time.now
61
57
  threads = []
62
58
  buffer = []
@@ -76,4 +72,30 @@ class ThreadPoolTest < MiniTest::Test
76
72
  assert_equal @pool.size, threads.uniq.size
77
73
  assert_equal (0..9).to_a, buffer.sort
78
74
  end
75
+
76
+ def test_busy?
77
+ assert_equal false, @pool.busy?
78
+
79
+ f = spin do
80
+ @pool.process { sleep 0.001 }
81
+ end
82
+
83
+ snooze
84
+ assert_equal true, @pool.busy?
85
+ f.await
86
+
87
+ assert_equal false, @pool.busy?
88
+ end
89
+
90
+ def test_default_thread_pool_process
91
+ current_thread = Thread.current
92
+
93
+ processing_thread = nil
94
+ result = Polyphony::ThreadPool.process do
95
+ processing_thread = Thread.current
96
+ +'foo' + 'bar'
97
+ end
98
+ assert_equal 'foobar', result
99
+ assert processing_thread != current_thread
100
+ end
79
101
  end
@@ -9,6 +9,7 @@ class ThrottlerTest < MiniTest::Test
9
9
  f = spin { loop { t.process { buffer << 1 } } }
10
10
  sleep 0.02
11
11
  f.stop
12
+ snooze
12
13
  assert buffer.size >= 2
13
14
  assert buffer.size <= 3
14
15
  ensure
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.29'
4
+ version: '0.30'
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-02-02 00:00:00.000000000 Z
11
+ date: 2020-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -266,24 +266,27 @@ files:
266
266
  - docs/getting-started/installing.md
267
267
  - docs/getting-started/tutorial.md
268
268
  - docs/index.md
269
- - docs/technical-overview.md
270
- - docs/technical-overview/concurrency.md
271
- - docs/technical-overview/design-principles.md
272
- - docs/technical-overview/exception-handling.md
273
- - docs/technical-overview/extending.md
274
- - docs/technical-overview/fiber-scheduling.md
269
+ - docs/main-concepts.md
270
+ - docs/main-concepts/concurrency.md
271
+ - docs/main-concepts/design-principles.md
272
+ - docs/main-concepts/exception-handling.md
273
+ - docs/main-concepts/extending.md
274
+ - docs/main-concepts/fiber-scheduling.md
275
275
  - docs/user-guide.md
276
276
  - docs/user-guide/all-about-timers.md
277
277
  - docs/user-guide/web-server.md
278
278
  - examples/core/01-spinning-up-fibers.rb
279
279
  - examples/core/02-awaiting-fibers.rb
280
280
  - examples/core/03-interrupting.rb
281
+ - examples/core/xx-at_exit.rb
281
282
  - examples/core/xx-channels.rb
282
283
  - examples/core/xx-deadlock.rb
283
284
  - examples/core/xx-deferring-an-operation.rb
284
285
  - examples/core/xx-erlang-style-genserver.rb
286
+ - examples/core/xx-fork-terminate.rb
285
287
  - examples/core/xx-forking.rb
286
288
  - examples/core/xx-move_on.rb
289
+ - examples/core/xx-pingpong.rb
287
290
  - examples/core/xx-queue-async.rb
288
291
  - examples/core/xx-readpartial.rb
289
292
  - examples/core/xx-recurrent-timer.rb
@@ -295,6 +298,7 @@ files:
295
298
  - examples/core/xx-snooze-starve.rb
296
299
  - examples/core/xx-spin_error_backtrace.rb
297
300
  - examples/core/xx-state-machine.rb
301
+ - examples/core/xx-stop.rb
298
302
  - examples/core/xx-supervisors.rb
299
303
  - examples/core/xx-thread-selector-sleep.rb
300
304
  - examples/core/xx-thread-selector-snooze.rb
@@ -379,7 +383,6 @@ files:
379
383
  - lib/polyphony/core/exceptions.rb
380
384
  - lib/polyphony/core/global_api.rb
381
385
  - lib/polyphony/core/resource_pool.rb
382
- - lib/polyphony/core/supervisor.rb
383
386
  - lib/polyphony/core/sync.rb
384
387
  - lib/polyphony/core/thread_pool.rb
385
388
  - lib/polyphony/core/throttler.rb
@@ -391,7 +394,6 @@ files:
391
394
  - lib/polyphony/extensions/thread.rb
392
395
  - lib/polyphony/fs.rb
393
396
  - lib/polyphony/irb.rb
394
- - lib/polyphony/line_reader.rb
395
397
  - lib/polyphony/net.rb
396
398
  - lib/polyphony/postgres.rb
397
399
  - lib/polyphony/redis.rb
@@ -407,12 +409,10 @@ files:
407
409
  - test/test_ext.rb
408
410
  - test/test_fiber.rb
409
411
  - test/test_global_api.rb
410
- - test/test_gyro.rb
411
412
  - test/test_io.rb
412
413
  - test/test_kernel.rb
413
414
  - test/test_resource_pool.rb
414
415
  - test/test_signal.rb
415
- - test/test_supervisor.rb
416
416
  - test/test_thread.rb
417
417
  - test/test_thread_pool.rb
418
418
  - test/test_throttler.rb
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :Supervisor
4
-
5
- import '../extensions/fiber'
6
- Exceptions = import './exceptions'
7
-
8
- # Implements a supervision mechanism for controlling multiple fibers
9
- class Supervisor
10
- def initialize
11
- @fibers = []
12
- @pending = {}
13
- yield(self) if block_given?
14
- end
15
-
16
- def await(&block)
17
- @mode = :await
18
- @supervisor_fiber = Fiber.current
19
- block&.(self)
20
- suspend
21
- @fibers.map(&:result)
22
- rescue Exceptions::MoveOn => e
23
- e.value
24
- ensure
25
- finalize_await
26
- end
27
- alias_method :join, :await
28
-
29
- def select(&block)
30
- @mode = :select
31
- @select_fiber = nil
32
- @supervisor_fiber = Fiber.current
33
- block&.(self)
34
- suspend
35
- [@select_fiber.result, @select_fiber]
36
- rescue Exceptions::MoveOn => e
37
- e.value
38
- ensure
39
- finalize_select
40
- end
41
-
42
- def finalize_await
43
- if still_running?
44
- stop_all_tasks
45
- suspend
46
- else
47
- @supervisor_fiber = nil
48
- end
49
- end
50
-
51
- def finalize_select
52
- stop_all_tasks if still_running?
53
- @supervisor_fiber = nil
54
- end
55
-
56
- def spin(orig_caller = caller, &block)
57
- add Fiber.spin(orig_caller, &block)
58
- end
59
-
60
- def add(fiber)
61
- @fibers << fiber
62
- @pending[fiber] = true
63
- fiber.when_done { task_completed(fiber) }
64
- fiber
65
- end
66
- alias_method :<<, :add
67
-
68
- def still_running?
69
- !@pending.empty?
70
- end
71
-
72
- def interrupt(result = nil)
73
- return unless @supervisor_fiber && !@stopped
74
-
75
- @stopped = true
76
- @supervisor_fiber.schedule Exceptions::MoveOn.new(nil, result)
77
- end
78
- alias_method :stop, :interrupt
79
-
80
- def stop_all_tasks
81
- exception = Exceptions::MoveOn.new
82
- @pending.each_key do |c|
83
- c.schedule(exception)
84
- end
85
- end
86
-
87
- def task_completed(fiber)
88
- return unless @pending[fiber]
89
-
90
- @pending.delete(fiber)
91
- return unless @pending.empty? || (@mode == :select && !@select_fiber)
92
-
93
- @select_fiber = fiber if @mode == :select
94
- @supervisor_fiber&.schedule
95
- end
96
- end
97
-
98
- # Supervision extensions for Fiber class
99
- class ::Fiber
100
- class << self
101
- def await(*fibers)
102
- supervisor = Supervisor.new
103
- fibers.each { |f| supervisor << f }
104
- supervisor.await
105
- end
106
- alias_method :join, :await
107
-
108
- def select(*fibers)
109
- supervisor = Supervisor.new
110
- fibers.each { |f| supervisor << f }
111
- supervisor.select
112
- end
113
- end
114
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :LineReader
4
-
5
- Core = import('./core')
6
-
7
- # a stream that can read single lines from another stream
8
- class LineReader
9
- # Initializes the line reader with a source and optional line separator
10
- # @param source [Stream] source stream
11
- # @param sep [String] line separator
12
- def initialize(source = nil, sep = $/)
13
- @source = source
14
- if source
15
- source.on(:data) { |data| push(data) }
16
- source.on(:close) { close }
17
- source.on(:error) { |err| error(err) }
18
- end
19
- @read_buffer = +''
20
- @separator = sep
21
- @separator_size = sep.bytesize
22
- end
23
-
24
- # Pushes data into the read buffer and emits lines
25
- # @param data [String] data to be read
26
- # @return [void]
27
- def push(data)
28
- @read_buffer << data
29
- emit_lines
30
- end
31
-
32
- # Emits lines from the read buffer
33
- # @return [void]
34
- def emit_lines
35
- while (line = _gets)
36
- @lines_promise.resolve(line)
37
- end
38
- end
39
-
40
- # Returns a line sliced from the read buffer
41
- # @return [String] line
42
- def _gets
43
- idx = @read_buffer.index(@separator)
44
- idx && @read_buffer.slice!(0, idx + @separator_size)
45
- end
46
-
47
- def gets
48
- Core.promise do |p|
49
- @lines_promise = p
50
- end
51
- end
52
-
53
- # Returns a async generator of lines
54
- # @return [Promise] line generator
55
- def lines
56
- Core.generator do |p|
57
- @lines_promise = p
58
- end
59
- end
60
-
61
- # Iterates asynchronously over lines received
62
- # @return [void]
63
- def each_line(&block)
64
- lines.each(&block)
65
- end
66
-
67
- # Closes the stream and cancels any pending reads
68
- # @return [void]
69
- def close
70
- @lines_promise&.stop
71
- end
72
-
73
- # handles error generated by source
74
- # @param err [Exception] raised error
75
- # @return [void]
76
- def error(err)
77
- return unless @lines_promise
78
-
79
- @lines_promise.stop
80
- @lines_promise.reject(err)
81
- end
82
- end
data/test/test_gyro.rb DELETED
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'helper'
4
-
5
- class GyroTest < MiniTest::Test
6
- def test_break
7
- skip "break is still not implemented for new scheduler"
8
- values = []
9
- Fiber.spin do
10
- values << :foo
11
- snooze
12
- # here will never be reached
13
- values << :bar
14
- suspend
15
- end
16
-
17
- Fiber.spin do
18
- Gyro.break!
19
- end
20
-
21
- suspend
22
-
23
- assert_equal [:foo], values
24
- end
25
- end