polyphony 0.34 → 0.41

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +11 -10
  8. data/README.md +2 -1
  9. data/Rakefile +6 -2
  10. data/TODO.md +18 -95
  11. data/docs/_includes/head.html +40 -0
  12. data/docs/_includes/nav.html +5 -5
  13. data/docs/api-reference.md +1 -1
  14. data/docs/api-reference/fiber.md +18 -0
  15. data/docs/api-reference/gyro-async.md +57 -0
  16. data/docs/api-reference/gyro-child.md +29 -0
  17. data/docs/api-reference/gyro-queue.md +44 -0
  18. data/docs/api-reference/gyro-timer.md +51 -0
  19. data/docs/api-reference/gyro.md +25 -0
  20. data/docs/index.md +10 -7
  21. data/docs/main-concepts/design-principles.md +67 -9
  22. data/docs/main-concepts/extending.md +1 -1
  23. data/docs/main-concepts/fiber-scheduling.md +55 -72
  24. data/examples/core/xx-agent.rb +102 -0
  25. data/examples/core/xx-fork-cleanup.rb +22 -0
  26. data/examples/core/xx-sleeping.rb +14 -6
  27. data/examples/core/xx-timer-gc.rb +17 -0
  28. data/examples/io/tunnel.rb +48 -0
  29. data/examples/io/xx-irb.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  31. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  32. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  33. data/ext/polyphony/fiber.c +112 -0
  34. data/ext/{gyro → polyphony}/libev.c +0 -0
  35. data/ext/{gyro → polyphony}/libev.h +0 -0
  36. data/ext/polyphony/libev_agent.c +503 -0
  37. data/ext/polyphony/libev_queue.c +214 -0
  38. data/ext/polyphony/polyphony.c +89 -0
  39. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
  40. data/ext/polyphony/polyphony_ext.c +23 -0
  41. data/ext/{gyro → polyphony}/socket.c +21 -19
  42. data/ext/{gyro → polyphony}/thread.c +55 -119
  43. data/ext/{gyro → polyphony}/tracing.c +1 -1
  44. data/lib/polyphony.rb +37 -44
  45. data/lib/polyphony/adapters/fs.rb +1 -4
  46. data/lib/polyphony/adapters/irb.rb +2 -2
  47. data/lib/polyphony/adapters/postgres.rb +6 -5
  48. data/lib/polyphony/adapters/process.rb +27 -23
  49. data/lib/polyphony/adapters/trace.rb +110 -105
  50. data/lib/polyphony/core/channel.rb +35 -35
  51. data/lib/polyphony/core/exceptions.rb +29 -29
  52. data/lib/polyphony/core/global_api.rb +94 -91
  53. data/lib/polyphony/core/resource_pool.rb +83 -83
  54. data/lib/polyphony/core/sync.rb +16 -16
  55. data/lib/polyphony/core/thread_pool.rb +49 -37
  56. data/lib/polyphony/core/throttler.rb +30 -23
  57. data/lib/polyphony/event.rb +27 -0
  58. data/lib/polyphony/extensions/core.rb +23 -14
  59. data/lib/polyphony/extensions/fiber.rb +269 -267
  60. data/lib/polyphony/extensions/io.rb +56 -26
  61. data/lib/polyphony/extensions/openssl.rb +5 -9
  62. data/lib/polyphony/extensions/socket.rb +29 -10
  63. data/lib/polyphony/extensions/thread.rb +19 -12
  64. data/lib/polyphony/net.rb +64 -60
  65. data/lib/polyphony/version.rb +1 -1
  66. data/polyphony.gemspec +3 -6
  67. data/test/helper.rb +14 -1
  68. data/test/stress.rb +17 -12
  69. data/test/test_agent.rb +77 -0
  70. data/test/{test_async.rb → test_event.rb} +17 -9
  71. data/test/test_ext.rb +25 -4
  72. data/test/test_fiber.rb +23 -14
  73. data/test/test_global_api.rb +5 -5
  74. data/test/test_io.rb +46 -24
  75. data/test/test_queue.rb +74 -0
  76. data/test/test_signal.rb +3 -40
  77. data/test/test_socket.rb +33 -0
  78. data/test/test_thread.rb +38 -16
  79. data/test/test_thread_pool.rb +3 -3
  80. data/test/test_throttler.rb +0 -1
  81. data/test/test_trace.rb +6 -5
  82. metadata +34 -39
  83. data/ext/gyro/async.c +0 -158
  84. data/ext/gyro/child.c +0 -117
  85. data/ext/gyro/gyro.c +0 -203
  86. data/ext/gyro/gyro_ext.c +0 -31
  87. data/ext/gyro/io.c +0 -447
  88. data/ext/gyro/queue.c +0 -142
  89. data/ext/gyro/selector.c +0 -183
  90. data/ext/gyro/signal.c +0 -108
  91. data/ext/gyro/timer.c +0 -154
  92. data/test/test_timer.rb +0 -56
@@ -1,36 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :BaseException, :MoveOn, :Cancel, :Terminate, :Restart
4
-
5
- # Common exception class for interrupting fibers. These exceptions allow
6
- # control of fibers. BaseException exceptions can encapsulate a value and thus
7
- # provide a way to interrupt long-running blocking operations while still
8
- # passing a value back to the call site. BaseException exceptions can also
9
- # references a cancel scope in order to allow correct bubbling of exceptions
10
- # through nested cancel scopes.
11
- class BaseException < ::Exception
12
- attr_reader :value
13
-
14
- def initialize(value = nil)
15
- @caller_backtrace = caller
16
- @value = value
17
- end
18
-
19
- def backtrace
20
- sanitize(@caller_backtrace)
3
+ module Polyphony
4
+ # Common exception class for interrupting fibers. These exceptions allow
5
+ # control of fibers. BaseException exceptions can encapsulate a value and thus
6
+ # provide a way to interrupt long-running blocking operations while still
7
+ # passing a value back to the call site. BaseException exceptions can also
8
+ # references a cancel scope in order to allow correct bubbling of exceptions
9
+ # through nested cancel scopes.
10
+ class BaseException < ::Exception
11
+ attr_reader :value
12
+
13
+ def initialize(value = nil)
14
+ @caller_backtrace = caller
15
+ @value = value
16
+ end
17
+
18
+ def backtrace
19
+ sanitize(@caller_backtrace)
20
+ end
21
21
  end
22
- end
23
22
 
24
- # MoveOn is used to interrupt a long-running blocking operation, while
25
- # continuing the rest of the computation.
26
- class MoveOn < BaseException; end
23
+ # MoveOn is used to interrupt a long-running blocking operation, while
24
+ # continuing the rest of the computation.
25
+ class MoveOn < BaseException; end
27
26
 
28
- # Cancel is used to interrupt a long-running blocking operation, bubbling the
29
- # exception up through cancel scopes and supervisors.
30
- class Cancel < BaseException; end
27
+ # Cancel is used to interrupt a long-running blocking operation, bubbling the
28
+ # exception up through cancel scopes and supervisors.
29
+ class Cancel < BaseException; end
31
30
 
32
- # Terminate is used to interrupt a fiber once its parent fiber has terminated.
33
- class Terminate < BaseException; end
31
+ # Terminate is used to interrupt a fiber once its parent fiber has terminated.
32
+ class Terminate < BaseException; end
34
33
 
35
- # Restart is used to restart a fiber
36
- class Restart < BaseException; end
34
+ # Restart is used to restart a fiber
35
+ class Restart < BaseException; end
36
+ end
@@ -1,119 +1,122 @@
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
+ next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
49
+ loop do
50
+ now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
51
+ Thread.current.agent.sleep(next_time - now)
52
+ yield
53
+ loop do
54
+ next_time += interval
55
+ break if next_time > now
56
+ end
43
57
  end
44
- else
45
- Fiber.current.spin(tag, caller) { loop(&block) }
46
58
  end
47
- end
48
59
 
49
- def every(interval)
50
- timer = Gyro::Timer.new(interval, interval)
51
- loop do
52
- timer.await
53
- yield
60
+ def move_on_after(interval, with_value: nil, &block)
61
+ fiber = ::Fiber.current
62
+ unless block
63
+ return spin do
64
+ sleep interval
65
+ fiber.schedule with_value
66
+ end
67
+ end
68
+
69
+ move_on_after_with_block(fiber, interval, with_value, &block)
54
70
  end
55
- ensure
56
- timer.stop
57
- end
58
71
 
59
- def move_on_after(interval, with_value: nil, &block)
60
- fiber = ::Fiber.current
61
- unless block
62
- return spin do
72
+ def move_on_after_with_block(fiber, interval, with_value, &block)
73
+ canceller = spin do
63
74
  sleep interval
64
- fiber.schedule with_value
75
+ fiber.schedule Polyphony::MoveOn.new(with_value)
65
76
  end
77
+ block.call
78
+ rescue Polyphony::MoveOn => e
79
+ e.value
80
+ ensure
81
+ canceller.stop
66
82
  end
67
83
 
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)
84
+ def receive
85
+ Fiber.current.receive
75
86
  end
76
- block.call
77
- rescue Exceptions::MoveOn => e
78
- e.value
79
- ensure
80
- canceller.stop
81
- end
82
87
 
83
- def receive
84
- Fiber.current.receive
85
- end
86
-
87
- def receive_pending
88
- Fiber.current.receive_pending
89
- end
88
+ def receive_pending
89
+ Fiber.current.receive_pending
90
+ end
90
91
 
91
- def supervise(*args, &block)
92
- Fiber.current.supervise(*args, &block)
93
- end
92
+ def supervise(*args, &block)
93
+ Fiber.current.supervise(*args, &block)
94
+ end
94
95
 
95
- def sleep(duration = nil)
96
- return sleep_forever unless duration
96
+ def sleep(duration = nil)
97
+ return sleep_forever unless duration
97
98
 
98
- timer = Gyro::Timer.new(duration, 0)
99
- timer.await
100
- end
99
+ Thread.current.agent.sleep duration
100
+ end
101
101
 
102
- def sleep_forever
103
- Thread.current.fiber_ref
104
- suspend
105
- ensure
106
- Thread.current.fiber_unref
107
- end
102
+ def sleep_forever
103
+ Thread.current.fiber_ref
104
+ suspend
105
+ ensure
106
+ Thread.current.fiber_unref
107
+ end
108
108
 
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) }
109
+ def throttled_loop(rate, count: nil, &block)
110
+ throttler = Polyphony::Throttler.new(rate)
111
+ if count
112
+ count.times { |_i| throttler.(&block) }
113
+ else
114
+ loop { throttler.(&block) }
115
+ end
116
+ ensure
117
+ throttler&.stop
115
118
  end
116
- ensure
117
- throttler.stop
118
119
  end
119
120
  end
121
+
122
+ 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
+ Polyphony.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
+ Polyphony.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
107
  end