evt 0.1.4 → 0.3.0

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