polyphony 0.36 → 0.38

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