evt 0.3.1 → 0.3.6

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: 40b96a960c2027db56a915a8c4d6e20f8d29fd9f195003ef1a93cf9fad13a151
4
- data.tar.gz: d6f99c7c134954a47b68c56f9a68f1227ed9ac90cefedefbef33a9c8978730aa
3
+ metadata.gz: d7c2d7880e0a6e8232a91b2aa154adcc7705c87d3907b33beb27f3f7b10dcbac
4
+ data.tar.gz: 7d882336538396f34a3c54bb8ccc3dd34b9431f60709d9527c87e3136d0c646b
5
5
  SHA512:
6
- metadata.gz: 75dbf1e519db25b8a0f63958cb2578fe852f12374eaed0a5a3b9595b30315ad92971477db2b27ed1bd2db6bd051553fa93eaa41263ed45c08c57ace0b43117fd
7
- data.tar.gz: 905988d2a76fed454cd42b3acc014d5ddce044ecdf918352d8a6ede58213b3006dda7588a7d5889c6a4b16b23cce0a14308d71e4ba02430efcfe0fcdfa8f38c6
6
+ metadata.gz: 28f8fb3d3ed2b0c1ee797ba1a36974392e5e7d8c00b38c4f7b25f95c0bf759e2d7f20402f6cb7dc1399187cc4c03e7c0e8d80d641551cc815e20db992d014843
7
+ data.tar.gz: 90e7cb4395e297761462376990b5faef42598d517dcbc3d5b0001e08d5e39582d158739ba5461eb3700651166fb1041ab15b551dd50f1af002116abc3698cea9
data/README.md CHANGED
@@ -15,13 +15,13 @@ 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
24
+ 1. when liburing is installed. (Currently fixing)
25
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).
@@ -29,22 +29,20 @@ The Event Library that designed for Ruby 3.0 Fiber Scheduler.
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 |
45
-
46
- The benchmark uses an invalid parser, and `wrk` is very error-sensitive. The benchmark can't close the connection properly.
47
- Use a valid parser, recent updates to my [midori](https://github.com/midori-rb/midori.rb) is able to use Ruby scheduler, which could achives 247k+ req/s on single thread with `kqueue` and 647k+ req/s with `epoll`.
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 |
48
46
 
49
47
  ## Install
50
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,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Evt::Bundled
4
- MAXIMUM_TIMEOUT = 5
4
+ MAXIMUM_TIMEOUT = 5000
5
5
  COLLECT_COUNTER_MAX = 16384
6
6
 
7
7
  def initialize
@@ -23,10 +23,10 @@ class Evt::Bundled
23
23
  attr_reader :waiting
24
24
 
25
25
  def next_timeout
26
- _fiber, timeout = @waiting.min_by{|key, value| value}
26
+ _fiber, timeout = @waiting.min_by{ |key, value| value }
27
27
 
28
28
  if timeout
29
- offset = timeout - current_time
29
+ offset = (timeout - current_time) * 1000 # Use mililisecond
30
30
  return 0 if offset < 0
31
31
  return offset if offset < MAXIMUM_TIMEOUT
32
32
  end
@@ -49,9 +49,10 @@ class Evt::Bundled
49
49
  end
50
50
 
51
51
  unless iovs.nil?
52
- iovs&.each do |io|
52
+ iovs&.each do |v|
53
+ io, ret = v
53
54
  fiber = @iovs.delete(io)
54
- fiber&.resume
55
+ fiber&.resume(ret)
55
56
  end
56
57
  end
57
58
 
@@ -63,7 +64,7 @@ class Evt::Bundled
63
64
 
64
65
  waiting.each do |fiber, timeout|
65
66
  if timeout <= time
66
- fiber.resume
67
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
67
68
  else
68
69
  @waiting[fiber] = timeout
69
70
  end
@@ -78,7 +79,7 @@ class Evt::Bundled
78
79
  end
79
80
 
80
81
  ready.each do |fiber|
81
- fiber.resume
82
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
82
83
  end
83
84
  end
84
85
  end
@@ -150,8 +151,8 @@ class Evt::Bundled
150
151
  end
151
152
 
152
153
  # Collect closed streams in readables and writables
153
- def collect
154
- if @collect_counter < COLLECT_COUNTER_MAX
154
+ def collect(force=false)
155
+ if @collect_counter < COLLECT_COUNTER_MAX and !force
155
156
  @collect_counter += 1
156
157
  return
157
158
  end
@@ -165,6 +166,10 @@ class Evt::Bundled
165
166
  @writable.keys.each do |io|
166
167
  @writable.delete(io) if io.closed?
167
168
  end
169
+
170
+ @iovs.keys.each do |io|
171
+ @iovs.delete(io) if io.closed?
172
+ end
168
173
  end
169
174
 
170
175
  # Intercept the creation of a non-blocking fiber.
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evt
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.6"
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.1
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delton Ding
8
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