polyphony 0.36 → 0.38

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +0 -11
  4. data/Gemfile.lock +1 -3
  5. data/Rakefile +4 -0
  6. data/TODO.md +12 -10
  7. data/docs/index.md +2 -1
  8. data/examples/core/xx-fork-cleanup.rb +22 -0
  9. data/ext/gyro/async.c +27 -13
  10. data/ext/gyro/child.c +29 -15
  11. data/ext/gyro/fiber.c +3 -1
  12. data/ext/gyro/gyro.c +0 -6
  13. data/ext/gyro/gyro.h +6 -0
  14. data/ext/gyro/io.c +24 -9
  15. data/ext/gyro/queue.c +21 -21
  16. data/ext/gyro/selector.c +23 -0
  17. data/ext/gyro/signal.c +24 -9
  18. data/ext/gyro/thread.c +12 -2
  19. data/ext/gyro/timer.c +33 -18
  20. data/lib/polyphony.rb +27 -36
  21. data/lib/polyphony/adapters/fs.rb +1 -4
  22. data/lib/polyphony/adapters/process.rb +29 -25
  23. data/lib/polyphony/adapters/trace.rb +129 -124
  24. data/lib/polyphony/core/channel.rb +36 -36
  25. data/lib/polyphony/core/exceptions.rb +29 -29
  26. data/lib/polyphony/core/global_api.rb +92 -91
  27. data/lib/polyphony/core/resource_pool.rb +84 -84
  28. data/lib/polyphony/core/sync.rb +17 -17
  29. data/lib/polyphony/core/thread_pool.rb +49 -37
  30. data/lib/polyphony/core/throttler.rb +25 -25
  31. data/lib/polyphony/extensions/core.rb +3 -3
  32. data/lib/polyphony/extensions/fiber.rb +269 -267
  33. data/lib/polyphony/extensions/openssl.rb +1 -1
  34. data/lib/polyphony/extensions/socket.rb +2 -1
  35. data/lib/polyphony/extensions/thread.rb +3 -3
  36. data/lib/polyphony/net.rb +71 -67
  37. data/lib/polyphony/version.rb +1 -1
  38. data/polyphony.gemspec +0 -3
  39. data/test/stress.rb +17 -12
  40. data/test/test_thread.rb +1 -0
  41. data/test/test_thread_pool.rb +2 -2
  42. data/test/test_throttler.rb +0 -1
  43. metadata +3 -16
@@ -1,119 +1,120 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :API
4
-
5
- import '../extensions/core'
6
- import '../extensions/fiber'
7
-
8
- Exceptions = import '../core/exceptions'
9
- Throttler = import '../core/throttler'
3
+ require_relative '../extensions/core'
4
+ require_relative '../extensions/fiber'
5
+ require_relative './exceptions'
6
+ require_relative './throttler'
7
+
8
+ module Polyphony
9
+ # Global API methods to be included in ::Object
10
+ module GlobalAPI
11
+ def after(interval, &block)
12
+ spin do
13
+ sleep interval
14
+ block.()
15
+ end
16
+ end
10
17
 
11
- # Global API methods to be included in ::Object
12
- module API
13
- def after(interval, &block)
14
- spin do
15
- sleep interval
16
- block.()
18
+ def cancel_after(interval, &block)
19
+ fiber = ::Fiber.current
20
+ canceller = spin do
21
+ sleep interval
22
+ fiber.schedule Polyphony::Cancel.new
23
+ end
24
+ block ? cancel_after_wrap_block(canceller, &block) : canceller
17
25
  end
18
- end
19
26
 
20
- def cancel_after(interval, &block)
21
- fiber = ::Fiber.current
22
- canceller = spin do
23
- sleep interval
24
- fiber.schedule Exceptions::Cancel.new
27
+ def cancel_after_wrap_block(canceller, &block)
28
+ block.call
29
+ ensure
30
+ canceller.stop
25
31
  end
26
- block ? cancel_after_wrap_block(canceller, &block) : canceller
27
- end
28
32
 
29
- def cancel_after_wrap_block(canceller, &block)
30
- block.call
31
- ensure
32
- canceller.stop
33
- end
33
+ def spin(tag = nil, &block)
34
+ Fiber.current.spin(tag, caller, &block)
35
+ end
34
36
 
35
- def spin(tag = nil, &block)
36
- Fiber.current.spin(tag, caller, &block)
37
- end
37
+ def spin_loop(tag = nil, rate: nil, &block)
38
+ if rate
39
+ Fiber.current.spin(tag, caller) do
40
+ throttled_loop(rate, &block)
41
+ end
42
+ else
43
+ Fiber.current.spin(tag, caller) { loop(&block) }
44
+ end
45
+ end
38
46
 
39
- def spin_loop(tag = nil, rate: nil, &block)
40
- if rate
41
- Fiber.current.spin(tag, caller) do
42
- throttled_loop(rate, &block)
47
+ def every(interval)
48
+ timer = Gyro::Timer.new(interval, interval)
49
+ loop do
50
+ timer.await
51
+ yield
43
52
  end
44
- else
45
- Fiber.current.spin(tag, caller) { loop(&block) }
53
+ ensure
54
+ timer.stop
46
55
  end
47
- end
48
56
 
49
- def every(interval)
50
- timer = Gyro::Timer.new(interval, interval)
51
- loop do
52
- timer.await
53
- yield
57
+ def move_on_after(interval, with_value: nil, &block)
58
+ fiber = ::Fiber.current
59
+ unless block
60
+ return spin do
61
+ sleep interval
62
+ fiber.schedule with_value
63
+ end
64
+ end
65
+
66
+ move_on_after_with_block(fiber, interval, with_value, &block)
54
67
  end
55
- ensure
56
- timer.stop
57
- end
58
68
 
59
- def move_on_after(interval, with_value: nil, &block)
60
- fiber = ::Fiber.current
61
- unless block
62
- return spin do
69
+ def move_on_after_with_block(fiber, interval, with_value, &block)
70
+ canceller = spin do
63
71
  sleep interval
64
- fiber.schedule with_value
72
+ fiber.schedule Polyphony::MoveOn.new(with_value)
65
73
  end
74
+ block.call
75
+ rescue Polyphony::MoveOn => e
76
+ e.value
77
+ ensure
78
+ canceller.stop
66
79
  end
67
80
 
68
- move_on_after_with_block(fiber, interval, with_value, &block)
69
- end
70
-
71
- def move_on_after_with_block(fiber, interval, with_value, &block)
72
- canceller = spin do
73
- sleep interval
74
- fiber.schedule Exceptions::MoveOn.new(with_value)
81
+ def receive
82
+ Fiber.current.receive
75
83
  end
76
- block.call
77
- rescue Exceptions::MoveOn => e
78
- e.value
79
- ensure
80
- canceller.stop
81
- end
82
84
 
83
- def receive
84
- Fiber.current.receive
85
- end
86
-
87
- def receive_pending
88
- Fiber.current.receive_pending
89
- end
85
+ def receive_pending
86
+ Fiber.current.receive_pending
87
+ end
90
88
 
91
- def supervise(*args, &block)
92
- Fiber.current.supervise(*args, &block)
93
- end
89
+ def supervise(*args, &block)
90
+ Fiber.current.supervise(*args, &block)
91
+ end
94
92
 
95
- def sleep(duration = nil)
96
- return sleep_forever unless duration
93
+ def sleep(duration = nil)
94
+ return sleep_forever unless duration
97
95
 
98
- timer = Gyro::Timer.new(duration, 0)
99
- timer.await
100
- end
96
+ timer = Gyro::Timer.new(duration, 0)
97
+ timer.await
98
+ end
101
99
 
102
- def sleep_forever
103
- Thread.current.fiber_ref
104
- suspend
105
- ensure
106
- Thread.current.fiber_unref
107
- end
100
+ def sleep_forever
101
+ Thread.current.fiber_ref
102
+ suspend
103
+ ensure
104
+ Thread.current.fiber_unref
105
+ end
108
106
 
109
- def throttled_loop(rate, count: nil, &block)
110
- throttler = Throttler.new(rate)
111
- if count
112
- count.times { throttler.(&block) }
113
- else
114
- loop { throttler.(&block) }
107
+ def throttled_loop(rate, count: nil, &block)
108
+ throttler = Polyphony::Throttler.new(rate)
109
+ if count
110
+ count.times { throttler.(&block) }
111
+ else
112
+ loop { throttler.(&block) }
113
+ end
114
+ ensure
115
+ throttler.stop
115
116
  end
116
- ensure
117
- throttler.stop
118
117
  end
119
118
  end
119
+
120
+ Object.include Polyphony::GlobalAPI
@@ -1,107 +1,107 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :ResourcePool
4
-
5
- # Implements a limited resource pool
6
- class ResourcePool
7
- attr_reader :limit, :size
8
-
9
- # Initializes a new resource pool
10
- # @param opts [Hash] options
11
- # @param &block [Proc] allocator block
12
- def initialize(opts, &block)
13
- @allocator = block
3
+ module Polyphony
4
+ # Implements a limited resource pool
5
+ class ResourcePool
6
+ attr_reader :limit, :size
7
+
8
+ # Initializes a new resource pool
9
+ # @param opts [Hash] options
10
+ # @param &block [Proc] allocator block
11
+ def initialize(opts, &block)
12
+ @allocator = block
13
+
14
+ @stock = []
15
+ @queue = []
16
+
17
+ @limit = opts[:limit] || 4
18
+ @size = 0
19
+ end
14
20
 
15
- @stock = []
16
- @queue = []
21
+ def available
22
+ @stock.size
23
+ end
17
24
 
18
- @limit = opts[:limit] || 4
19
- @size = 0
20
- end
25
+ def acquire
26
+ Gyro.ref
27
+ resource = wait_for_resource
28
+ return unless resource
21
29
 
22
- def available
23
- @stock.size
24
- end
30
+ yield resource
31
+ ensure
32
+ Gyro.unref
33
+ release(resource) if resource
34
+ end
25
35
 
26
- def acquire
27
- Gyro.ref
28
- resource = wait_for_resource
29
- return unless resource
36
+ def wait_for_resource
37
+ fiber = Fiber.current
38
+ @queue << fiber
39
+ ready_resource = from_stock
40
+ return ready_resource if ready_resource
30
41
 
31
- yield resource
32
- ensure
33
- Gyro.unref
34
- release(resource) if resource
35
- end
42
+ suspend
43
+ ensure
44
+ @queue.delete(fiber)
45
+ end
36
46
 
37
- def wait_for_resource
38
- fiber = Fiber.current
39
- @queue << fiber
40
- ready_resource = from_stock
41
- return ready_resource if ready_resource
47
+ def release(resource)
48
+ if resource.__discarded__
49
+ @size -= 1
50
+ elsif resource
51
+ return_to_stock(resource)
52
+ dequeue
53
+ end
54
+ end
42
55
 
43
- suspend
44
- ensure
45
- @queue.delete(fiber)
46
- end
56
+ def dequeue
57
+ return if @queue.empty? || @stock.empty?
47
58
 
48
- def release(resource)
49
- if resource.__discarded__
50
- @size -= 1
51
- elsif resource
52
- return_to_stock(resource)
53
- dequeue
59
+ @queue.shift.schedule(@stock.shift)
54
60
  end
55
- end
56
-
57
- def dequeue
58
- return if @queue.empty? || @stock.empty?
59
61
 
60
- @queue.shift.schedule(@stock.shift)
61
- end
62
+ def return_to_stock(resource)
63
+ @stock << resource
64
+ end
62
65
 
63
- def return_to_stock(resource)
64
- @stock << resource
65
- end
66
+ def from_stock
67
+ @stock.shift || (@size < @limit && allocate)
68
+ end
66
69
 
67
- def from_stock
68
- @stock.shift || (@size < @limit && allocate)
69
- end
70
+ def method_missing(sym, *args, &block)
71
+ acquire { |r| r.send(sym, *args, &block) }
72
+ end
70
73
 
71
- def method_missing(sym, *args, &block)
72
- acquire { |r| r.send(sym, *args, &block) }
73
- end
74
+ def respond_to_missing?(*_args)
75
+ true
76
+ end
74
77
 
75
- def respond_to_missing?(*_args)
76
- true
77
- end
78
+ # Extension to allow discarding of resources
79
+ module ResourceExtensions
80
+ def __discarded__
81
+ @__discarded__
82
+ end
78
83
 
79
- # Extension to allow discarding of resources
80
- module ResourceExtensions
81
- def __discarded__
82
- @__discarded__
84
+ def __discard__
85
+ @__discarded__ = true
86
+ end
83
87
  end
84
88
 
85
- def __discard__
86
- @__discarded__ = true
89
+ # Allocates a resource
90
+ # @return [any] allocated resource
91
+ def allocate
92
+ @size += 1
93
+ @allocator.().tap { |r| r.extend ResourceExtensions }
87
94
  end
88
- end
89
-
90
- # Allocates a resource
91
- # @return [any] allocated resource
92
- def allocate
93
- @size += 1
94
- @allocator.().tap { |r| r.extend ResourceExtensions }
95
- end
96
95
 
97
- def <<(resource)
98
- @size += 1
99
- resource.extend ResourceExtensions
100
- @stock << resource
101
- dequeue
102
- end
96
+ def <<(resource)
97
+ @size += 1
98
+ resource.extend ResourceExtensions
99
+ @stock << resource
100
+ dequeue
101
+ end
103
102
 
104
- def preheat!
105
- (@limit - @size).times { @stock << allocate }
103
+ def preheat!
104
+ (@limit - @size).times { @stock << allocate }
105
+ end
106
106
  end
107
- end
107
+ end
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :Mutex
3
+ module Polyphony
4
+ # Implements mutex lock for synchronizing access to a shared resource
5
+ class Mutex
6
+ def initialize
7
+ @waiting_fibers = Gyro::Queue.new
8
+ end
4
9
 
5
- # Implements mutex lock for synchronizing access to a shared resource
6
- class Mutex
7
- def initialize
8
- @waiting_fibers = []
10
+ def synchronize
11
+ fiber = Fiber.current
12
+ @waiting_fibers << fiber
13
+ suspend if @waiting_fibers.size > 1
14
+ yield
15
+ ensure
16
+ @waiting_fibers.delete(fiber)
17
+ @waiting_fibers.first&.schedule
18
+ snooze
19
+ end
9
20
  end
10
-
11
- def synchronize
12
- fiber = Fiber.current
13
- @waiting_fibers << fiber
14
- suspend if @waiting_fibers.size > 1
15
- yield
16
- ensure
17
- @waiting_fibers.delete(fiber)
18
- @waiting_fibers.first&.schedule
19
- snooze
20
- end
21
- end
21
+ end
@@ -1,52 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :ThreadPool
4
-
5
3
  require 'etc'
6
4
 
7
- # Implements a pool of threads
8
- class ThreadPool
9
- attr_reader :size
5
+ module Polyphony
6
+ # Implements a pool of threads
7
+ class ThreadPool
8
+ attr_reader :size
10
9
 
11
- def self.process(&block)
12
- @default_pool ||= new
13
- @default_pool.process(&block)
14
- end
10
+ def self.process(&block)
11
+ @default_pool ||= self.new
12
+ @default_pool.process(&block)
13
+ end
15
14
 
16
- def initialize(size = Etc.nprocessors)
17
- @size = size
18
- @task_queue = ::Queue.new
19
- @threads = (1..@size).map { Thread.new { thread_loop } }
20
- end
15
+ def self.reset
16
+ return unless @default_pool
17
+
18
+ @default_pool.stop
19
+ @default_pool = nil
20
+ end
21
21
 
22
- def process(&block)
23
- setup unless @task_queue
22
+ def initialize(size = Etc.nprocessors)
23
+ @size = size
24
+ @task_queue = Gyro::Queue.new
25
+ @threads = (1..@size).map { Thread.new { thread_loop } }
26
+ end
24
27
 
25
- async = Fiber.current.auto_async
26
- @task_queue << [block, async]
27
- async.await
28
- end
28
+ def process(&block)
29
+ setup unless @task_queue
29
30
 
30
- def cast(&block)
31
- setup unless @task_queue
31
+ async = Fiber.current.auto_async
32
+ @task_queue << [block, async]
33
+ async.await
34
+ end
32
35
 
33
- @task_queue << [block, nil]
34
- self
35
- end
36
+ def cast(&block)
37
+ setup unless @task_queue
36
38
 
37
- def busy?
38
- !@task_queue.empty?
39
- end
39
+ @task_queue << [block, nil]
40
+ self
41
+ end
40
42
 
41
- def thread_loop
42
- loop { run_queued_task }
43
- end
43
+ def busy?
44
+ !@task_queue.empty?
45
+ end
46
+
47
+ def thread_loop
48
+ loop { run_queued_task }
49
+ end
50
+
51
+ def run_queued_task
52
+ (block, watcher) = @task_queue.pop
53
+ result = block.()
54
+ watcher&.signal(result)
55
+ rescue Exception => e
56
+ watcher ? watcher.signal(e) : raise(e)
57
+ end
44
58
 
45
- def run_queued_task
46
- (block, watcher) = @task_queue.pop
47
- result = block.()
48
- watcher&.signal(result)
49
- rescue Exception => e
50
- watcher ? watcher.signal(e) : raise(e)
59
+ def stop
60
+ @threads.each(&:kill)
61
+ @threads.each(&:join)
62
+ end
51
63
  end
52
64
  end