evt 0.3.1 → 0.3.6

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 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