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.
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/io'
5
- require_relative 'evt_ext'
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
@@ -1,162 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fiber'
4
- require 'socket'
5
-
6
- begin
7
- require 'io/nonblock'
8
- rescue LoadError
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
- def initialize
14
- @readable = {}
15
- @writable = {}
16
- @waiting = {}
17
- @blocking = []
18
-
19
- @ios = ObjectSpace::WeakMap.new
20
- init_selector
21
- end
22
-
23
- attr :readable
24
- attr :writable
25
- attr :waiting
26
- attr :blocking
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
- waiting.each do |fiber, timeout|
63
- if timeout <= time
64
- fiber.resume
65
- else
66
- @waiting[fiber] = timeout
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