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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +11 -2
- data/.gitignore +2 -2
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +0 -11
- data/Gemfile.lock +11 -10
- data/README.md +2 -1
- data/Rakefile +6 -2
- data/TODO.md +18 -95
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/nav.html +5 -5
- data/docs/api-reference.md +1 -1
- data/docs/api-reference/fiber.md +18 -0
- data/docs/api-reference/gyro-async.md +57 -0
- data/docs/api-reference/gyro-child.md +29 -0
- data/docs/api-reference/gyro-queue.md +44 -0
- data/docs/api-reference/gyro-timer.md +51 -0
- data/docs/api-reference/gyro.md +25 -0
- data/docs/index.md +10 -7
- data/docs/main-concepts/design-principles.md +67 -9
- data/docs/main-concepts/extending.md +1 -1
- data/docs/main-concepts/fiber-scheduling.md +55 -72
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-irb.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/polyphony/fiber.c +112 -0
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +503 -0
- data/ext/polyphony/libev_queue.c +214 -0
- data/ext/polyphony/polyphony.c +89 -0
- data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
- data/ext/polyphony/polyphony_ext.c +23 -0
- data/ext/{gyro → polyphony}/socket.c +21 -19
- data/ext/{gyro → polyphony}/thread.c +55 -119
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +37 -44
- data/lib/polyphony/adapters/fs.rb +1 -4
- data/lib/polyphony/adapters/irb.rb +2 -2
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +27 -23
- data/lib/polyphony/adapters/trace.rb +110 -105
- data/lib/polyphony/core/channel.rb +35 -35
- data/lib/polyphony/core/exceptions.rb +29 -29
- data/lib/polyphony/core/global_api.rb +94 -91
- data/lib/polyphony/core/resource_pool.rb +83 -83
- data/lib/polyphony/core/sync.rb +16 -16
- data/lib/polyphony/core/thread_pool.rb +49 -37
- data/lib/polyphony/core/throttler.rb +30 -23
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +23 -14
- data/lib/polyphony/extensions/fiber.rb +269 -267
- data/lib/polyphony/extensions/io.rb +56 -26
- data/lib/polyphony/extensions/openssl.rb +5 -9
- data/lib/polyphony/extensions/socket.rb +29 -10
- data/lib/polyphony/extensions/thread.rb +19 -12
- data/lib/polyphony/net.rb +64 -60
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +3 -6
- data/test/helper.rb +14 -1
- data/test/stress.rb +17 -12
- data/test/test_agent.rb +77 -0
- data/test/{test_async.rb → test_event.rb} +17 -9
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +23 -14
- data/test/test_global_api.rb +5 -5
- data/test/test_io.rb +46 -24
- data/test/test_queue.rb +74 -0
- data/test/test_signal.rb +3 -40
- data/test/test_socket.rb +33 -0
- data/test/test_thread.rb +38 -16
- data/test/test_thread_pool.rb +3 -3
- data/test/test_throttler.rb +0 -1
- data/test/test_trace.rb +6 -5
- metadata +34 -39
- data/ext/gyro/async.c +0 -158
- data/ext/gyro/child.c +0 -117
- data/ext/gyro/gyro.c +0 -203
- data/ext/gyro/gyro_ext.c +0 -31
- data/ext/gyro/io.c +0 -447
- data/ext/gyro/queue.c +0 -142
- data/ext/gyro/selector.c +0 -183
- data/ext/gyro/signal.c +0 -108
- data/ext/gyro/timer.c +0 -154
- data/test/test_timer.rb +0 -56
@@ -1,36 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
canceller.stop
|
33
|
-
end
|
33
|
+
def spin(tag = nil, &block)
|
34
|
+
Fiber.current.spin(tag, caller, &block)
|
35
|
+
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
92
|
+
def supervise(*args, &block)
|
93
|
+
Fiber.current.supervise(*args, &block)
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
96
|
+
def sleep(duration = nil)
|
97
|
+
return sleep_forever unless duration
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
end
|
99
|
+
Thread.current.agent.sleep duration
|
100
|
+
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
def sleep_forever
|
103
|
+
Thread.current.fiber_ref
|
104
|
+
suspend
|
105
|
+
ensure
|
106
|
+
Thread.current.fiber_unref
|
107
|
+
end
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
21
|
+
def available
|
22
|
+
@stock.size
|
23
|
+
end
|
17
24
|
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
def acquire
|
26
|
+
Polyphony.ref
|
27
|
+
resource = wait_for_resource
|
28
|
+
return unless resource
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
|
30
|
+
yield resource
|
31
|
+
ensure
|
32
|
+
Polyphony.unref
|
33
|
+
release(resource) if resource
|
34
|
+
end
|
25
35
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
42
|
+
suspend
|
43
|
+
ensure
|
44
|
+
@queue.delete(fiber)
|
45
|
+
end
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
@queue.delete(fiber)
|
46
|
-
end
|
56
|
+
def dequeue
|
57
|
+
return if @queue.empty? || @stock.empty?
|
47
58
|
|
48
|
-
|
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
|
-
|
61
|
-
|
62
|
+
def return_to_stock(resource)
|
63
|
+
@stock << resource
|
64
|
+
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
+
def from_stock
|
67
|
+
@stock.shift || (@size < @limit && allocate)
|
68
|
+
end
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
def method_missing(sym, *args, &block)
|
71
|
+
acquire { |r| r.send(sym, *args, &block) }
|
72
|
+
end
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
def respond_to_missing?(*_args)
|
75
|
+
true
|
76
|
+
end
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
+
# Extension to allow discarding of resources
|
79
|
+
module ResourceExtensions
|
80
|
+
def __discarded__
|
81
|
+
@__discarded__
|
82
|
+
end
|
78
83
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@__discarded__
|
84
|
+
def __discard__
|
85
|
+
@__discarded__ = true
|
86
|
+
end
|
83
87
|
end
|
84
88
|
|
85
|
-
|
86
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
96
|
+
def <<(resource)
|
97
|
+
@size += 1
|
98
|
+
resource.extend ResourceExtensions
|
99
|
+
@stock << resource
|
100
|
+
dequeue
|
101
|
+
end
|
103
102
|
|
104
|
-
|
105
|
-
|
103
|
+
def preheat!
|
104
|
+
(@limit - @size).times { @stock << allocate }
|
105
|
+
end
|
106
106
|
end
|
107
107
|
end
|