polyphony 0.34 → 0.41

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