evt 0.3.0 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f49f37d3240b1878b00acaeea453e00c9dae2a96c8c7f12889836d653d6b1835
4
- data.tar.gz: 2af4b0238768dcb2b4112eaff379cad3899633eab69017517f36d5d51ba1fae7
3
+ metadata.gz: a62836e0e4fd3eb50837a3112e54c0ee723eac5c7d732db6d4be05e0e2c63989
4
+ data.tar.gz: 1a10b744a967d71094fdc34e4678a23339c48997ca57c30b46e1e7a848de74bf
5
5
  SHA512:
6
- metadata.gz: 58222d04ff91f47667697fd63d6d235d85c8204e09a82ac18dbd05b27d37eb79d6bcda034117684e2d49cd0cd67d2818df5264a4f73751851f1ebefdbe3a9de2
7
- data.tar.gz: 17c54e28beb805e30a3883446a807c5114287ba3c37b402a85dcba013f2910e8aca7d29c17efe527dd050a971f224ff0e8f4f2aacd36ef8cbe7970ff8aa06d56
6
+ metadata.gz: 3bbc6ab2e1e30cc456d29206cac4fb05b13e83415451880b1155024b052d0fb4e0c2abc90a928e5562a0dd926ea9ad67e11f3c9b2e0082fa5cd9918e5b099247
7
+ data.tar.gz: a4a76eafa9f45ffb9e8c886568bc26c213c02e36283d31b54badc7d80b561ab076c476301b344cd78e5f0c7ba5fd8d543169829cf23ab4119fe69461b29e9f20
data/README.md CHANGED
@@ -15,33 +15,34 @@ The Event Library that designed for Ruby 3.0 Fiber Scheduler.
15
15
 
16
16
  | | Linux | Windows | macOS | FreeBSD |
17
17
  | --------------- | ----------- | ------------| ----------- | ----------- |
18
- | io_uring | (See 1) | ❌ | ❌ | ❌ |
18
+ | io_uring | ⚠️ (See 1) | ❌ | ❌ | ❌ |
19
19
  | epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
20
20
  | kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
21
21
  | IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
22
22
  | Ruby (`IO.select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
23
23
 
24
- 1. when liburing is installed
25
- 2. when kernel version >= 2.6.8
24
+ 1. when liburing is installed. (Currently fixing)
25
+ 2. when kernel version >= 2.6.9
26
26
  3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
27
27
  4. Some I/Os are not able to be nonblock under Windows. See [Scheduler Docs](https://docs.ruby-lang.org/en/master/doc/scheduler_md.html#label-IO).
28
28
  5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
29
29
 
30
30
  ### Benchmark
31
31
 
32
- The benchmark is running under `v0.2.2` version. See [evt-server-benchmark](https://github.com/dsh0416/evt-server-benchmark) for test code, the test is running under a single-thread server.
32
+ The benchmark is running under `v0.3.1` version. See `example.rb` in [midori](https://github.com/midori-rb/midori.rb) for test code, the test is running under a single-thread server.
33
33
 
34
34
  The test command is `wrk -t4 -c8192 -d30s http://localhost:3001`.
35
35
 
36
36
  All of the systems have set their file descriptor limit to maximum.
37
-
38
- | OS | CPU | Memory | Backend | req/s |
39
- | ----- | ----------- | ------ | ---------------------- | -------- |
40
- | Linux | Ryzen 2700x | 64GB | epoll | 54680.08 |
41
- | Linux | Ryzen 2700x | 64GB | io_uring | 50245.53 |
42
- | Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 44159.23 |
43
- | macOS | i7-6820HQ | 16GB | kqueue | 37855.53 |
44
- | macOS | i7-6820HQ | 16GB | IO.select (using poll) | 28293.36 |
37
+ On systems raising "Fiber unable to allocate memory", `sudo sysctl -w vm.max_map_count=1000000` is set.
38
+
39
+ | OS | CPU | Memory | Backend | req/s |
40
+ | ----- | ----------- | ------ | ---------------------- | --------------|
41
+ | Linux | Ryzen 2700x | 64GB | epoll | 1853259.47 |
42
+ | Linux | Ryzen 2700x | 64GB | io_uring | require fixes |
43
+ | Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 1636849.15 |
44
+ | macOS | i7-6820HQ | 16GB | kqueue | 247370.37 |
45
+ | macOS | i7-6820HQ | 16GB | IO.select (using poll) | 323391.38 |
45
46
 
46
47
  ## Install
47
48
 
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_development_dependency 'rake-compiler', '~> 1.0'
27
27
  spec.add_development_dependency 'simplecov', '~> 0.20.0'
28
+ spec.add_development_dependency 'minitest-reporters', '~> 1.4'
28
29
  end
@@ -3,6 +3,7 @@
3
3
  #include "evt.h"
4
4
 
5
5
  #if HAVE_SYS_EPOLL_H
6
+
6
7
  VALUE method_scheduler_epoll_init(VALUE self) {
7
8
  rb_iv_set(self, "@epfd", INT2NUM(epoll_create(1))); // Size of epoll is ignored after Linux 2.6.8.
8
9
  return Qnil;
@@ -31,14 +32,6 @@ VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest) {
31
32
  return Qnil;
32
33
  }
33
34
 
34
- VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io) {
35
- ID id_fileno = rb_intern("fileno");
36
- int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
37
- int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
38
- epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
39
- return Qnil;
40
- }
41
-
42
35
  VALUE method_scheduler_epoll_wait(VALUE self) {
43
36
  int n, epfd, i, event_flag, timeout;
44
37
  VALUE next_timeout, obj_io, readables, writables, result;
@@ -82,6 +75,14 @@ VALUE method_scheduler_epoll_wait(VALUE self) {
82
75
  return result;
83
76
  }
84
77
 
78
+ VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io) {
79
+ ID id_fileno = rb_intern("fileno");
80
+ int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
81
+ int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
82
+ epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
83
+ return Qnil;
84
+ }
85
+
85
86
  VALUE method_scheduler_epoll_backend(VALUE klass) {
86
87
  return rb_str_new_cstr("epoll");
87
88
  }
@@ -13,7 +13,6 @@ void Init_evt_ext();
13
13
  #if HAVE_LIBURING_H
14
14
  VALUE method_scheduler_uring_init(VALUE self);
15
15
  VALUE method_scheduler_uring_register(VALUE self, VALUE io, VALUE interest);
16
- VALUE method_scheduler_uring_deregister(VALUE self, VALUE io);
17
16
  VALUE method_scheduler_uring_wait(VALUE self);
18
17
  VALUE method_scheduler_uring_backend(VALUE klass);
19
18
  VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
@@ -55,7 +54,6 @@ void Init_evt_ext();
55
54
  #if HAVE_SYS_EVENT_H
56
55
  VALUE method_scheduler_kqueue_init(VALUE self);
57
56
  VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest);
58
- VALUE method_scheduler_kqueue_deregister(VALUE self, VALUE io);
59
57
  VALUE method_scheduler_kqueue_wait(VALUE self);
60
58
  VALUE method_scheduler_kqueue_backend(VALUE klass);
61
59
  #include <sys/event.h>
@@ -3,19 +3,22 @@
3
3
  #include "evt.h"
4
4
 
5
5
  VALUE method_scheduler_select_wait(VALUE self) {
6
- // return IO.select(@readable.keys, @writable.keys, [], next_timeout)
7
- VALUE readable, writable, readable_keys, writable_keys, next_timeout;
6
+ // return IO.select(@readable.keys, @writable.keys, [], next_timeout / 1000.0)
8
7
  ID id_select = rb_intern("select");
9
8
  ID id_next_timeout = rb_intern("next_timeout");
9
+ ID id_div = rb_intern("/");
10
+ ID id_to_f = rb_intern("to_f");
10
11
 
11
- readable = rb_iv_get(self, "@readable");
12
- writable = rb_iv_get(self, "@writable");
12
+ VALUE readable = rb_iv_get(self, "@readable");
13
+ VALUE writable = rb_iv_get(self, "@writable");
13
14
 
14
- readable_keys = rb_funcall(readable, rb_intern("keys"), 0);
15
- writable_keys = rb_funcall(writable, rb_intern("keys"), 0);
16
- next_timeout = rb_funcall(self, id_next_timeout, 0);
15
+ VALUE readable_keys = rb_funcall(readable, rb_intern("keys"), 0);
16
+ VALUE writable_keys = rb_funcall(writable, rb_intern("keys"), 0);
17
+ VALUE next_timeout = rb_funcall(self, id_next_timeout, 0);
18
+ next_timeout = rb_funcall(next_timeout, id_to_f, 0);
19
+ VALUE secs = rb_funcall(next_timeout, id_div, 1, DBL2NUM(1000.0));
17
20
 
18
- return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
21
+ return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), secs);
19
22
  }
20
23
 
21
24
  VALUE method_scheduler_select_backend(VALUE klass) {
@@ -79,6 +79,12 @@ VALUE method_scheduler_uring_wait(VALUE self) {
79
79
  iovs = rb_ary_new();
80
80
 
81
81
  TypedData_Get_Struct(rb_iv_get(self, "@ring"), struct io_uring, &type_uring_payload, ring);
82
+
83
+ struct __kernel_timespec ts;
84
+ ts.tv_sec = NUM2INT(next_timeout);
85
+ ts.tv_nsec = 0;
86
+
87
+ io_uring_wait_cqe_timeout(ring, cqes, &ts);
82
88
  ret = io_uring_peek_batch_cqe(ring, cqes, URING_MAX_EVENTS);
83
89
 
84
90
  for (i = 0; i < ret; i++) {
@@ -94,21 +100,14 @@ VALUE method_scheduler_uring_wait(VALUE self) {
94
100
  rb_funcall(writables, id_push, 1, obj_io);
95
101
  }
96
102
  } else {
97
- rb_funcall(iovs, id_push, 1, obj_io);
103
+ VALUE v = rb_ary_new2(2);
104
+ rb_ary_store(v, 0, obj_io);
105
+ rb_ary_store(v, 1, obj_io);
106
+ rb_funcall(iovs, id_push, 1, SIZET2NUM(cqes[i]->res));
98
107
  }
99
108
  io_uring_cqe_seen(ring, cqes[i]);
100
109
  }
101
110
 
102
- if (ret == 0) {
103
- if (next_timeout != Qnil && NUM2INT(next_timeout) != -1) {
104
- // sleep
105
- time = next_timeout / 1000;
106
- rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(time));
107
- } else {
108
- rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(0.001)); // To avoid infinite loop
109
- }
110
- }
111
-
112
111
  result = rb_ary_new2(3);
113
112
  rb_ary_store(result, 0, readables);
114
113
  rb_ary_store(result, 1, writables);
@@ -132,27 +131,23 @@ VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE o
132
131
  int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
133
132
 
134
133
  read_buffer = (char*) xmalloc(NUM2SIZET(length));
135
- struct iovec iov = {
136
- .iov_base = read_buffer,
137
- .iov_len = NUM2SIZET(length),
138
- };
139
134
 
140
135
  data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
141
136
  data->is_poll = false;
142
137
  data->io = io;
143
138
  data->poll_mask = 0;
144
139
 
145
- io_uring_prep_readv(sqe, fd, &iov, 1, NUM2SIZET(offset));
140
+ io_uring_prep_read(sqe, fd, read_buffer, 1, NUM2SIZET(length), NUM2SIZET(offset));
146
141
  io_uring_sqe_set_data(sqe, data);
147
142
  io_uring_submit(ring);
148
143
 
144
+ VALUE ret = rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
145
+
149
146
  VALUE result = rb_str_new(read_buffer, strlen(read_buffer));
150
147
  if (buffer != Qnil) {
151
148
  rb_str_append(buffer, result);
152
149
  }
153
-
154
- rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
155
- return result;
150
+ return ret;
156
151
  }
157
152
 
158
153
  VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
@@ -170,21 +165,16 @@ VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE
170
165
  int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
171
166
 
172
167
  write_buffer = StringValueCStr(buffer);
173
- struct iovec iov = {
174
- .iov_base = write_buffer,
175
- .iov_len = NUM2SIZET(length),
176
- };
177
168
 
178
169
  data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
179
170
  data->is_poll = false;
180
171
  data->io = io;
181
172
  data->poll_mask = 0;
182
173
 
183
- io_uring_prep_writev(sqe, fd, &iov, 1, NUM2SIZET(offset));
174
+ io_uring_prep_write(sqe, fd, write_buffer, NUM2SIZET(length), NUM2SIZET(offset));
184
175
  io_uring_sqe_set_data(sqe, data);
185
176
  io_uring_submit(ring);
186
- rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
187
- return length;
177
+ return rb_funcall(Fiber, rb_intern("yield"), 0);
188
178
  }
189
179
 
190
180
  VALUE method_scheduler_uring_backend(VALUE klass) {
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Evt::Bundled
4
- MAXIMUM_TIMEOUT = 5
4
+ MAXIMUM_TIMEOUT = 5000
5
+ COLLECT_COUNTER_MAX = 16384
5
6
 
6
7
  def initialize
7
8
  @readable = {}
@@ -12,6 +13,7 @@ class Evt::Bundled
12
13
  @lock = Mutex.new
13
14
  @blocking = 0
14
15
  @ready = []
16
+ @collect_counter = 0
15
17
 
16
18
  init_selector
17
19
  end
@@ -21,10 +23,10 @@ class Evt::Bundled
21
23
  attr_reader :waiting
22
24
 
23
25
  def next_timeout
24
- _fiber, timeout = @waiting.min_by{|key, value| value}
26
+ _fiber, timeout = @waiting.min_by{ |key, value| value }
25
27
 
26
28
  if timeout
27
- offset = timeout - current_time
29
+ offset = (timeout - current_time) * 1000 # Use mililisecond
28
30
  return 0 if offset < 0
29
31
  return offset if offset < MAXIMUM_TIMEOUT
30
32
  end
@@ -47,19 +49,22 @@ class Evt::Bundled
47
49
  end
48
50
 
49
51
  unless iovs.nil?
50
- iovs&.each do |io|
52
+ iovs&.each do |v|
53
+ io, ret = v
51
54
  fiber = @iovs.delete(io)
52
- fiber&.resume
55
+ fiber&.resume(ret)
53
56
  end
54
57
  end
55
58
 
59
+ collect
60
+
56
61
  if @waiting.any?
57
62
  time = current_time
58
63
  waiting, @waiting = @waiting, {}
59
64
 
60
65
  waiting.each do |fiber, timeout|
61
66
  if timeout <= time
62
- fiber.resume
67
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
63
68
  else
64
69
  @waiting[fiber] = timeout
65
70
  end
@@ -74,7 +79,7 @@ class Evt::Bundled
74
79
  end
75
80
 
76
81
  ready.each do |fiber|
77
- fiber.resume
82
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
78
83
  end
79
84
  end
80
85
  end
@@ -145,6 +150,28 @@ class Evt::Bundled
145
150
  self.run
146
151
  end
147
152
 
153
+ # Collect closed streams in readables and writables
154
+ def collect(force=false)
155
+ if @collect_counter < COLLECT_COUNTER_MAX and !force
156
+ @collect_counter += 1
157
+ return
158
+ end
159
+
160
+ @collect_counter = 0
161
+
162
+ @readable.keys.each do |io|
163
+ @readable.delete(io) if io.closed?
164
+ end
165
+
166
+ @writable.keys.each do |io|
167
+ @writable.delete(io) if io.closed?
168
+ end
169
+
170
+ @iovs.keys.each do |io|
171
+ @iovs.delete(io) if io.closed?
172
+ end
173
+ end
174
+
148
175
  # Intercept the creation of a non-blocking fiber.
149
176
  # @returns [Fiber]
150
177
  def fiber(&block)
@@ -16,7 +16,6 @@ class Evt::Iocp < Evt::Bundled
16
16
  end
17
17
 
18
18
  def deregister(io)
19
- # Placeholder
20
19
  end
21
20
 
22
21
  def io_read(io, buffer, offset, length)
@@ -18,7 +18,6 @@ class Evt::Kqueue < Evt::Bundled
18
18
  end
19
19
 
20
20
  def deregister(io)
21
- # Kqueue running under one-shot mode, no need to deregister
22
21
  end
23
22
 
24
23
  def wait
@@ -18,10 +18,12 @@ class Evt::Select < Evt::Bundled
18
18
  end
19
19
 
20
20
  def deregister(io)
21
- # Select is stateless
22
21
  end
23
22
 
24
23
  def wait
25
24
  select_wait
25
+ rescue Errno::EBADF => _
26
+ collect(true)
27
+ return [], []
26
28
  end
27
29
  end
@@ -14,11 +14,10 @@ class Evt::Uring < Evt::Bundled
14
14
  end
15
15
 
16
16
  def register(io, interest)
17
- uring_register(io, register)
17
+ uring_register(io, interest)
18
18
  end
19
19
 
20
20
  def deregister(io)
21
- # io_uring running under one-shot mode, no need to deregister
22
21
  end
23
22
 
24
23
  def io_read(io, buffer, offset, length)
@@ -10,7 +10,7 @@ class Evt::Scheduler
10
10
  class << self
11
11
  BACKENDS = [
12
12
  Evt::Uring,
13
- Evt::Epoll,
13
+ # Evt::Epoll,
14
14
  Evt::Kqueue,
15
15
  Evt::Iocp,
16
16
  Evt::Select,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evt
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delton Ding
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-22 00:00:00.000000000 Z
11
+ date: 2020-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.20.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-reporters
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
41
55
  description: A low-level Event Handler designed for Ruby 3 Scheduler for better performance
42
56
  email:
43
57
  - dsh0416@gmail.com
@@ -78,7 +92,7 @@ licenses:
78
92
  metadata:
79
93
  homepage_uri: https://github.com/dsh0416/evt
80
94
  source_code_uri: https://github.com/dsh0416/evt
81
- post_install_message:
95
+ post_install_message:
82
96
  rdoc_options: []
83
97
  require_paths:
84
98
  - lib
@@ -94,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
108
  version: '0'
95
109
  requirements: []
96
110
  rubygems_version: 3.2.2
97
- signing_key:
111
+ signing_key:
98
112
  specification_version: 4
99
113
  summary: The Event Library that designed for Ruby 3.0 Fiber Scheluer.
100
114
  test_files: []