evt 0.1.4 → 0.3.0
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/build.yml +21 -0
- data/.github/workflows/test.yml +40 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.md +66 -13
- data/Rakefile +1 -0
- data/evt.gemspec +4 -3
- data/ext/evt/epoll.h +89 -0
- data/ext/evt/evt.c +40 -213
- data/ext/evt/evt.h +85 -6
- data/ext/evt/extconf.rb +20 -2
- data/ext/evt/iocp.h +122 -0
- data/ext/evt/kqueue.h +82 -0
- data/ext/evt/select.h +24 -0
- data/ext/evt/uring.h +195 -0
- data/lib/evt.rb +10 -2
- data/lib/evt/backends/bundled.rb +155 -0
- data/lib/evt/backends/epoll.rb +27 -0
- data/lib/evt/backends/iocp.rb +33 -0
- data/lib/evt/backends/kqueue.rb +27 -0
- data/lib/evt/backends/select.rb +27 -0
- data/lib/evt/backends/uring.rb +35 -0
- data/lib/evt/scheduler.rb +26 -152
- data/lib/evt/version.rb +1 -1
- metadata +32 -8
- data/.travis.yml +0 -25
- data/Gemfile.lock +0 -24
- data/lib/evt/io.rb +0 -9
data/lib/evt.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fiber'
|
4
|
+
require 'socket'
|
5
|
+
require 'io/nonblock'
|
3
6
|
require_relative 'evt/version'
|
4
|
-
require_relative 'evt/
|
5
|
-
require_relative '
|
7
|
+
require_relative 'evt/backends/bundled'
|
8
|
+
require_relative 'evt/backends/epoll'
|
9
|
+
require_relative 'evt/backends/iocp'
|
10
|
+
require_relative 'evt/backends/kqueue'
|
11
|
+
require_relative 'evt/backends/select'
|
12
|
+
require_relative 'evt/backends/uring'
|
6
13
|
require_relative 'evt/scheduler'
|
14
|
+
require_relative 'evt_ext'
|
7
15
|
|
8
16
|
module Evt
|
9
17
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Bundled
|
4
|
+
MAXIMUM_TIMEOUT = 5
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@readable = {}
|
8
|
+
@writable = {}
|
9
|
+
@waiting = {}
|
10
|
+
@iovs = {}
|
11
|
+
|
12
|
+
@lock = Mutex.new
|
13
|
+
@blocking = 0
|
14
|
+
@ready = []
|
15
|
+
|
16
|
+
init_selector
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :readable
|
20
|
+
attr_reader :writable
|
21
|
+
attr_reader :waiting
|
22
|
+
|
23
|
+
def next_timeout
|
24
|
+
_fiber, timeout = @waiting.min_by{|key, value| value}
|
25
|
+
|
26
|
+
if timeout
|
27
|
+
offset = timeout - current_time
|
28
|
+
return 0 if offset < 0
|
29
|
+
return offset if offset < MAXIMUM_TIMEOUT
|
30
|
+
end
|
31
|
+
|
32
|
+
MAXIMUM_TIMEOUT
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @blocking.positive?
|
37
|
+
readable, writable, iovs = self.wait
|
38
|
+
|
39
|
+
readable&.each do |io|
|
40
|
+
fiber = @readable.delete(io)
|
41
|
+
fiber&.resume
|
42
|
+
end
|
43
|
+
|
44
|
+
writable&.each do |io|
|
45
|
+
fiber = @writable.delete(io)
|
46
|
+
fiber&.resume
|
47
|
+
end
|
48
|
+
|
49
|
+
unless iovs.nil?
|
50
|
+
iovs&.each do |io|
|
51
|
+
fiber = @iovs.delete(io)
|
52
|
+
fiber&.resume
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if @waiting.any?
|
57
|
+
time = current_time
|
58
|
+
waiting, @waiting = @waiting, {}
|
59
|
+
|
60
|
+
waiting.each do |fiber, timeout|
|
61
|
+
if timeout <= time
|
62
|
+
fiber.resume
|
63
|
+
else
|
64
|
+
@waiting[fiber] = timeout
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if @ready.any?
|
70
|
+
ready = nil
|
71
|
+
|
72
|
+
@lock.synchronize do
|
73
|
+
ready, @ready = @ready, []
|
74
|
+
end
|
75
|
+
|
76
|
+
ready.each do |fiber|
|
77
|
+
fiber.resume
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def current_time
|
84
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Wait for the given file descriptor to match the specified events within
|
88
|
+
# the specified timeout.
|
89
|
+
# @parameter event [Integer] A bit mask of `IO::READABLE`,
|
90
|
+
# `IO::WRITABLE` and `IO::PRIORITY`.
|
91
|
+
# @parameter timeout [Numeric] The amount of time to wait for the event in seconds.
|
92
|
+
# @returns [Integer] The subset of events that are ready.
|
93
|
+
def io_wait(io, events, duration)
|
94
|
+
# TODO: IO::PRIORITY
|
95
|
+
@readable[io] = Fiber.current unless (events & IO::READABLE).zero?
|
96
|
+
@writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
|
97
|
+
self.register(io, events)
|
98
|
+
Fiber.yield
|
99
|
+
self.deregister(io)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sleep the current task for the specified duration, or forever if not
|
104
|
+
# specified.
|
105
|
+
# @param duration [Numeric] The amount of time to sleep in seconds.
|
106
|
+
def kernel_sleep(duration = nil)
|
107
|
+
self.block(:sleep, duration)
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Block the calling fiber.
|
112
|
+
# @parameter blocker [Object] What we are waiting on, informational only.
|
113
|
+
# @parameter timeout [Numeric | Nil] The amount of time to wait for in seconds.
|
114
|
+
# @returns [Boolean] Whether the blocking operation was successful or not.
|
115
|
+
def block(blocker, timeout = nil)
|
116
|
+
if timeout
|
117
|
+
@waiting[Fiber.current] = current_time + timeout
|
118
|
+
begin
|
119
|
+
Fiber.yield
|
120
|
+
ensure
|
121
|
+
@waiting.delete(Fiber.current)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
@blocking += 1
|
125
|
+
begin
|
126
|
+
Fiber.yield
|
127
|
+
ensure
|
128
|
+
@blocking -= 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Unblock the specified fiber.
|
134
|
+
# @parameter blocker [Object] What we are waiting on, informational only.
|
135
|
+
# @parameter fiber [Fiber] The fiber to unblock.
|
136
|
+
# @reentrant Thread safe.
|
137
|
+
def unblock(blocker, fiber)
|
138
|
+
@lock.synchronize do
|
139
|
+
@ready << fiber
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Invoked when the thread exits.
|
144
|
+
def close
|
145
|
+
self.run
|
146
|
+
end
|
147
|
+
|
148
|
+
# Intercept the creation of a non-blocking fiber.
|
149
|
+
# @returns [Fiber]
|
150
|
+
def fiber(&block)
|
151
|
+
fiber = Fiber.new(blocking: false, &block)
|
152
|
+
fiber.resume
|
153
|
+
fiber
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Epoll < Evt::Bundled
|
4
|
+
def self.available?
|
5
|
+
self.respond_to?(:epoll_backend)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.backend
|
9
|
+
self.epoll_backend
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_selector
|
13
|
+
epoll_init_selector
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(io, interest)
|
17
|
+
epoll_register(io, interest)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deregister(io)
|
21
|
+
epoll_deregister(io)
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
epoll_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Iocp < Evt::Bundled
|
4
|
+
##
|
5
|
+
# IOCP is totally disabled for now
|
6
|
+
def self.available?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def init_selector
|
11
|
+
# Placeholder
|
12
|
+
end
|
13
|
+
|
14
|
+
def register(io, interest)
|
15
|
+
# Placeholder
|
16
|
+
end
|
17
|
+
|
18
|
+
def deregister(io)
|
19
|
+
# Placeholder
|
20
|
+
end
|
21
|
+
|
22
|
+
def io_read(io, buffer, offset, length)
|
23
|
+
# Placeholder
|
24
|
+
end
|
25
|
+
|
26
|
+
def io_write(io, buffer, offset, length)
|
27
|
+
# Placeholder
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait
|
31
|
+
# Placeholder
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Kqueue < Evt::Bundled
|
4
|
+
def self.available?
|
5
|
+
self.respond_to?(:kqueue_backend)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.backend
|
9
|
+
self.kqueue_backend
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_selector
|
13
|
+
kqueue_init_selector
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(io, interest)
|
17
|
+
kqueue_register(io, interest)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deregister(io)
|
21
|
+
# Kqueue running under one-shot mode, no need to deregister
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
kqueue_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Select < Evt::Bundled
|
4
|
+
def self.available?
|
5
|
+
self.respond_to?(:select_backend)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.backend
|
9
|
+
self.select_backend
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_selector
|
13
|
+
# Select is stateless
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(io, interest)
|
17
|
+
# Select is stateless
|
18
|
+
end
|
19
|
+
|
20
|
+
def deregister(io)
|
21
|
+
# Select is stateless
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
select_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Evt::Uring < Evt::Bundled
|
4
|
+
def self.available?
|
5
|
+
self.respond_to?(:uring_backend)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.backend
|
9
|
+
self.uring_backend
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_selector
|
13
|
+
uring_init_selector
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(io, interest)
|
17
|
+
uring_register(io, register)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deregister(io)
|
21
|
+
# io_uring running under one-shot mode, no need to deregister
|
22
|
+
end
|
23
|
+
|
24
|
+
def io_read(io, buffer, offset, length)
|
25
|
+
uring_io_read(io, buffer, offset, length)
|
26
|
+
end
|
27
|
+
|
28
|
+
def io_write(io, buffer, offset, length)
|
29
|
+
uring_io_write(io, buffer, offset, length)
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait
|
33
|
+
uring_wait
|
34
|
+
end
|
35
|
+
end
|
data/lib/evt/scheduler.rb
CHANGED
@@ -1,162 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Ignore.
|
10
|
-
end
|
11
|
-
|
3
|
+
##
|
4
|
+
# The major class for Ruby Fiber Scheduler
|
5
|
+
# @example
|
6
|
+
# scheduler = Evt::Scheduler.new
|
7
|
+
# Fiber.set_scheduler scheduler
|
8
|
+
# scheduler.run
|
12
9
|
class Evt::Scheduler
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
def next_timeout
|
29
|
-
_fiber, timeout = @waiting.min_by{|key, value| value}
|
30
|
-
|
31
|
-
if timeout
|
32
|
-
offset = timeout - current_time
|
33
|
-
|
34
|
-
if offset < 0
|
35
|
-
return 0
|
36
|
-
else
|
37
|
-
return offset
|
10
|
+
class << self
|
11
|
+
BACKENDS = [
|
12
|
+
Evt::Uring,
|
13
|
+
Evt::Epoll,
|
14
|
+
Evt::Kqueue,
|
15
|
+
Evt::Iocp,
|
16
|
+
Evt::Select,
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
##
|
20
|
+
# Returns the fastest possible scheduler
|
21
|
+
# Use the backend scheduler directly if you want to choose it yourself
|
22
|
+
def new
|
23
|
+
BACKENDS.each do |backend|
|
24
|
+
return backend.new if backend.available?
|
38
25
|
end
|
39
26
|
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def run
|
43
|
-
while @readable.any? or @writable.any? or @waiting.any?
|
44
|
-
readable, writable = self.wait
|
45
|
-
|
46
|
-
# puts "readable: #{readable}" if readable&.any?
|
47
|
-
# puts "writable: #{writable}" if writable&.any?
|
48
|
-
|
49
|
-
readable&.each do |io|
|
50
|
-
@readable[io]&.resume
|
51
|
-
end
|
52
|
-
|
53
|
-
writable&.each do |io|
|
54
|
-
@writable[io]&.resume
|
55
|
-
end
|
56
|
-
|
57
|
-
if @waiting.any?
|
58
|
-
time = current_time
|
59
|
-
waiting = @waiting
|
60
|
-
@waiting = {}
|
61
27
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
28
|
+
##
|
29
|
+
# Returns all available backends on this machine
|
30
|
+
def availables
|
31
|
+
BACKENDS.filter do |backend|
|
32
|
+
backend.available?
|
69
33
|
end
|
70
34
|
end
|
71
35
|
end
|
72
|
-
|
73
|
-
def for_fd(fd)
|
74
|
-
@ios[fd] ||= ::IO.for_fd(fd, autoclose: false)
|
75
|
-
end
|
76
|
-
|
77
|
-
def wait_readable(io)
|
78
|
-
@readable[io] = Fiber.current
|
79
|
-
self.register(io, IO::WAIT_READABLE)
|
80
|
-
Fiber.yield
|
81
|
-
@readable.delete(io)
|
82
|
-
self.deregister(io)
|
83
|
-
return true
|
84
|
-
end
|
85
|
-
|
86
|
-
def wait_readable_fd(fd)
|
87
|
-
wait_readable(
|
88
|
-
for_fd(fd)
|
89
|
-
)
|
90
|
-
end
|
91
|
-
|
92
|
-
def wait_writable(io)
|
93
|
-
@writable[io] = Fiber.current
|
94
|
-
self.register(io, IO::WAIT_WRITABLE)
|
95
|
-
Fiber.yield
|
96
|
-
@writable.delete(io)
|
97
|
-
self.deregister(io)
|
98
|
-
return true
|
99
|
-
end
|
100
|
-
|
101
|
-
def wait_writable_fd(fd)
|
102
|
-
wait_writable(
|
103
|
-
for_fd(fd)
|
104
|
-
)
|
105
|
-
end
|
106
|
-
|
107
|
-
def current_time
|
108
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
109
|
-
end
|
110
|
-
|
111
|
-
def wait_sleep(duration = nil)
|
112
|
-
@waiting[Fiber.current] = current_time + duration
|
113
|
-
|
114
|
-
Fiber.yield
|
115
|
-
|
116
|
-
return true
|
117
|
-
end
|
118
|
-
|
119
|
-
def wait_any(io, events, duration)
|
120
|
-
unless (events & IO::WAIT_READABLE).zero?
|
121
|
-
@readable[io] = Fiber.current
|
122
|
-
end
|
123
|
-
|
124
|
-
unless (events & IO::WAIT_WRITABLE).zero?
|
125
|
-
@writable[io] = Fiber.current
|
126
|
-
end
|
127
|
-
|
128
|
-
self.register(io, events)
|
129
|
-
|
130
|
-
Fiber.yield
|
131
|
-
|
132
|
-
@readable.delete(io)
|
133
|
-
@writable.delete(io)
|
134
|
-
self.deregister(io)
|
135
|
-
|
136
|
-
return true
|
137
|
-
end
|
138
|
-
|
139
|
-
|
140
|
-
def wait_for_single_fd(fd, events, duration)
|
141
|
-
wait_any(
|
142
|
-
for_fd(fd),
|
143
|
-
events,
|
144
|
-
duration
|
145
|
-
)
|
146
|
-
end
|
147
|
-
|
148
|
-
def enter_blocking_region
|
149
|
-
# puts "Enter blocking region: #{caller.first}"
|
150
|
-
end
|
151
|
-
|
152
|
-
def exit_blocking_region
|
153
|
-
# puts "Exit blocking region: #{caller.first}"
|
154
|
-
@blocking << caller.first
|
155
|
-
end
|
156
|
-
|
157
|
-
def fiber(&block)
|
158
|
-
fiber = Fiber.new(blocking: false, &block)
|
159
|
-
fiber.resume
|
160
|
-
return fiber
|
161
|
-
end
|
162
36
|
end
|