evt 0.3.0 → 0.3.5

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: 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: []