polyphony 0.29 → 0.30

Sign up to get free protection for your applications and to get access to all the features.
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