evt 0.3.2 → 0.4.0

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: 945c1a802be8b4e03b88c44138a139367288409b27c83a6f2904d0ecc94d2a40
4
- data.tar.gz: 7b88e16f4a082f536c5b0eb9a7f68e67e6e1f5ab4586dbe771f7c57b9004447a
3
+ metadata.gz: 3275dc2e231b57c3e740671f2d431ede41ca23339ba799b280edeed1c7be8d2c
4
+ data.tar.gz: 4107ecb57ec3ff7e566187013348a056773ac654eb2b369b7fa8bb057675750e
5
5
  SHA512:
6
- metadata.gz: f042da98ff5f85de3b4fce59a2726f2f4c46a1da8817abb524d700cd0413fc4118cb6c0a766a69abdc7e4ae5f0acdd35bc653b0c33116239c1e776f4c3218f23
7
- data.tar.gz: 1824a4935d712e62268b9cad01f52c445ccecfcaa859b11fd93749f28ef3f259370b011af395ae269c9c03544b7ab5c1bb6ad3afd18cc8eea4a6d576e151ff76
6
+ metadata.gz: db3172fe0ee6776480a64ee036aedb03b09ee5d5c8a5088fe68e4a969d7058964e8c55b004bb69962f61bffdb13a1c6c28ce49d339f8194e616ae44d877a9616
7
+ data.tar.gz: 94e287ddb35ae5f99e15ebf27ef76ce54ce3272008fe7c535483706111a8a39ff3168d2289135a027934d45c4382b51527c240fc9d7fbf6b9ce13f2ca7b016e3
data/README.md CHANGED
@@ -19,30 +19,59 @@ The Event Library that designed for Ruby 3.0 Fiber Scheduler.
19
19
  | epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
20
20
  | kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
21
21
  | IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
22
- | Ruby (`IO.select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
22
+ | Ruby (`IO.select`) | ✅ (See 6) | ✅ (⚠️See 4) | ✅ (See 6) | ✅ (See 6) |
23
23
 
24
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
- 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).
27
+ 4. Some I/Os are not able to be nonblock under Windows. Using POSIX select, **SLOW**. 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
+ 6. Using poll
29
30
 
30
31
  ### Benchmark
31
32
 
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
+ The benchmark is running under `v0.3.6` 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
34
 
34
- The test command is `wrk -t4 -c8192 -d30s http://localhost:3001`.
35
+ The test command is `wrk -t4 -c8192 -d30s http://localhost:8080`.
35
36
 
36
37
  All of the systems have set their file descriptor limit to maximum.
37
38
  On systems raising "Fiber unable to allocate memory", `sudo sysctl -w vm.max_map_count=1000000` is set.
38
39
 
39
40
  | OS | CPU | Memory | Backend | req/s |
40
- | ----- | ----------- | ------ | ---------------------- | --------------|
41
- | Linux | Ryzen 2700x | 64GB | epoll | 1853259.47 |
41
+ | ----- | ----------- | ------ | ---------------------- | ------------- |
42
+ | Linux | Ryzen 2700x | 64GB | epoll | 2035742.59 |
42
43
  | 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 |
44
+ | Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 1837640.54 |
45
+ | macOS | i7-6820HQ | 16GB | kqueue | 257821.78 |
46
+ | macOS | i7-6820HQ | 16GB | IO.select (using poll) | 338392.12 |
47
+
48
+ We also test the server with Redis request, with a [monkey-patched redis library](https://github.com/midori-rb/midori-contrib/blob/master/lib/midori-contrib/redic.rb). The example code is following:
49
+
50
+ ```ruby
51
+ require 'evt'
52
+ require 'midori'
53
+ require 'midori-contrib/redic'
54
+
55
+ Fiber.set_scheduler Evt::Scheduler.new
56
+ REDIS = Redic.new
57
+
58
+ class HelloWorldAPI < Midori::API
59
+ get '/' do
60
+ REDIS.call 'GET', 'foo'
61
+ end
62
+ end
63
+
64
+ Fiber.schedule do
65
+ Midori::Runner.new(HelloWorldAPI).start
66
+ end
67
+ ```
68
+
69
+ The benckmark result is as following:
70
+
71
+ | OS | CPU | Memory | Backend | req/s |
72
+ | ----- | ----------- | ------ | ------- | --------- |
73
+ | Linux | Ryzen 2700x | 64GB | epoll | 378060.30 |
74
+ | macOS | i7-6820HQ | 16GB | kqueue | 204460.32 |
46
75
 
47
76
  ## Install
48
77
 
@@ -74,6 +103,15 @@ end
74
103
  # "Hello World"
75
104
  ```
76
105
 
106
+ | | Windows `WaitFor...` | Windows `select` | Windows IOCP | `io_uring` | `epoll` | `kqueue` | `poll` | *NIX `select` |
107
+ | -------------- | -------------------- | ---------------- | ------------ | ---------- | ------- | -------- | ------ | ------------- |
108
+ | Anonymous Pipe | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
109
+ | Named Pipe | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
110
+ | Socket | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
111
+ | File | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
112
+
113
+
114
+
77
115
  ## Roadmap
78
116
 
79
117
  - [x] Support epoll/kqueue/select
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = "A low-level Event Handler designed for Ruby 3 Scheduler for better performance"
11
11
  spec.homepage = "https://github.com/dsh0416/evt"
12
12
  spec.license = 'BSD-3-Clause'
13
- spec.required_ruby_version = '>= 3.0.0.rc1'
13
+ spec.required_ruby_version = '>= 3.0.0'
14
14
 
15
15
  spec.metadata["homepage_uri"] = spec.homepage
16
16
  spec.metadata["source_code_uri"] = "https://github.com/dsh0416/evt"
@@ -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;
@@ -25,7 +26,7 @@ VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest) {
25
26
  event.events |= EPOLLOUT;
26
27
  }
27
28
 
28
- event.events |=EPOLLONESHOT;
29
+ event.events |= EPOLLRDHUP;
29
30
 
30
31
  event.data.ptr = (void*) io;
31
32
 
@@ -35,14 +36,13 @@ VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest) {
35
36
 
36
37
  VALUE method_scheduler_epoll_wait(VALUE self) {
37
38
  int n, epfd, i, event_flag, timeout;
38
- VALUE next_timeout, obj_io, readables, writables, result;
39
+ VALUE next_timeout, obj_io, iovs, result;
39
40
  ID id_next_timeout = rb_intern("next_timeout");
40
41
  ID id_push = rb_intern("push");
41
42
 
42
43
  epfd = NUM2INT(rb_iv_get(self, "@epfd"));
43
44
  next_timeout = rb_funcall(self, id_next_timeout, 0);
44
- readables = rb_ary_new();
45
- writables = rb_ary_new();
45
+ iovs = rb_ary_new();
46
46
 
47
47
  if (next_timeout == Qnil) {
48
48
  timeout = -1;
@@ -59,23 +59,28 @@ VALUE method_scheduler_epoll_wait(VALUE self) {
59
59
 
60
60
  for (i = 0; i < n; i++) {
61
61
  event_flag = events[i].events;
62
- if (event_flag & EPOLLIN) {
63
- obj_io = (VALUE) events[i].data.ptr;
64
- rb_funcall(readables, id_push, 1, obj_io);
65
- }
66
-
67
- if (event_flag & EPOLLOUT) {
68
- obj_io = (VALUE) events[i].data.ptr;
69
- rb_funcall(writables, id_push, 1, obj_io);
70
- }
62
+ obj_io = (VALUE) events[i].data.ptr;
63
+ VALUE e = rb_ary_new2(2);
64
+ rb_ary_store(e, 0, obj_io);
65
+ rb_ary_store(e, 1, INT2NUM(event_flag));
66
+ rb_funcall(iovs, id_push, 1, e);
71
67
  }
72
68
 
73
- result = rb_ary_new2(2);
74
- rb_ary_store(result, 0, readables);
75
- rb_ary_store(result, 1, writables);
69
+ result = rb_ary_new2(3);
70
+ rb_ary_store(result, 0, rb_ary_new());
71
+ rb_ary_store(result, 1, rb_ary_new());
72
+ rb_ary_store(result, 2, iovs);
76
73
  return result;
77
74
  }
78
75
 
76
+ VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io) {
77
+ ID id_fileno = rb_intern("fileno");
78
+ int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
79
+ int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
80
+ epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
81
+ return Qnil;
82
+ }
83
+
79
84
  VALUE method_scheduler_epoll_backend(VALUE klass) {
80
85
  return rb_str_new_cstr("epoll");
81
86
  }
@@ -24,6 +24,7 @@ void Init_evt_ext()
24
24
  rb_define_singleton_method(Bundled, "epoll_backend", method_scheduler_epoll_backend, 0);
25
25
  rb_define_method(Bundled, "epoll_init_selector", method_scheduler_epoll_init, 0);
26
26
  rb_define_method(Bundled, "epoll_register", method_scheduler_epoll_register, 2);
27
+ rb_define_method(Bundled, "epoll_deregister", method_scheduler_epoll_deregister, 1);
27
28
  rb_define_method(Bundled, "epoll_wait", method_scheduler_epoll_wait, 0);
28
29
  #endif
29
30
  #if HAVE_SYS_EVENT_H
@@ -45,6 +45,7 @@ void Init_evt_ext();
45
45
  #if HAVE_SYS_EPOLL_H
46
46
  VALUE method_scheduler_epoll_init(VALUE self);
47
47
  VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest);
48
+ VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io);
48
49
  VALUE method_scheduler_epoll_wait(VALUE self);
49
50
  VALUE method_scheduler_epoll_backend(VALUE klass);
50
51
  #include <sys/epoll.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) {
@@ -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
@@ -26,7 +26,7 @@ class Evt::Bundled
26
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
@@ -101,6 +101,7 @@ class Evt::Bundled
101
101
  @writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
102
102
  self.register(io, events)
103
103
  Fiber.yield
104
+ self.deregister(io)
104
105
  true
105
106
  end
106
107
 
@@ -17,6 +17,60 @@ class Evt::Epoll < Evt::Bundled
17
17
  epoll_register(io, interest)
18
18
  end
19
19
 
20
+ def deregister(io)
21
+ epoll_deregister(io)
22
+ end
23
+
24
+ def io_wait(io, events, duration)
25
+ @iovs[io] = Fiber.current
26
+ self.register(io, events)
27
+ Fiber.yield
28
+ self.deregister(io)
29
+ true
30
+ end
31
+
32
+ # def io_read(io, buffer, offset, length)
33
+ # result = +('')
34
+
35
+ # self.register(io, IO::READABLE)
36
+ # direct_read = io.read_nonblock(length, exception: false)
37
+
38
+ # unless direct_read == :wait_readable
39
+ # result << direct_read
40
+ # end
41
+
42
+ # until result.length >= length or io.closed? or io.eof?
43
+ # @iovs[io] = Fiber.current
44
+ # Fiber.yield
45
+ # todo = length - result.length
46
+ # result << io.read_nonblock(todo)
47
+ # end
48
+
49
+ # self.deregister(io)
50
+ # buffer[offset...offset+result.length] = result
51
+ # result.length
52
+ # end
53
+
54
+ # def io_write(io, buffer, offset, length)
55
+ # finished = 0
56
+
57
+ # self.register(io, IO::WRITABLE)
58
+ # direct_write = io.write_nonblock(buffer.byteslice(offset+finished..-1), exception: false)
59
+ # unless direct_write == :wait_writable
60
+ # finished += direct_write
61
+ # end
62
+
63
+ # until finished >= length or io.closed?
64
+ # @iovs[io] = Fiber.current
65
+ # Fiber.yield
66
+ # finished += io.write_nonblock(buffer.byteslice(offset+finished..-1))
67
+ # end
68
+
69
+ # self.deregister(io)
70
+
71
+ # finished
72
+ # end
73
+
20
74
  def wait
21
75
  epoll_wait
22
76
  end
@@ -15,6 +15,9 @@ class Evt::Iocp < Evt::Bundled
15
15
  # Placeholder
16
16
  end
17
17
 
18
+ def deregister(io)
19
+ end
20
+
18
21
  def io_read(io, buffer, offset, length)
19
22
  # Placeholder
20
23
  end
@@ -17,6 +17,9 @@ class Evt::Kqueue < Evt::Bundled
17
17
  kqueue_register(io, interest)
18
18
  end
19
19
 
20
+ def deregister(io)
21
+ end
22
+
20
23
  def wait
21
24
  kqueue_wait
22
25
  end
@@ -17,8 +17,14 @@ class Evt::Select < Evt::Bundled
17
17
  # Select is stateless
18
18
  end
19
19
 
20
+ def deregister(io)
21
+ end
22
+
20
23
  def wait
21
24
  select_wait
25
+ rescue IOError => _
26
+ collect(true)
27
+ return [], []
22
28
  rescue Errno::EBADF => _
23
29
  collect(true)
24
30
  return [], []
@@ -17,13 +17,17 @@ class Evt::Uring < Evt::Bundled
17
17
  uring_register(io, interest)
18
18
  end
19
19
 
20
- def io_read(io, buffer, offset, length)
21
- uring_io_read(io, buffer, offset, length)
20
+ def deregister(io)
22
21
  end
23
22
 
24
- def io_write(io, buffer, offset, length)
25
- uring_io_write(io, buffer, offset, length)
26
- end
23
+ # Disable direct I/Os for now
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
27
31
 
28
32
  def wait
29
33
  uring_wait
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evt
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.0"
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.2
4
+ version: 0.4.0
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-23 00:00:00.000000000 Z
11
+ date: 2020-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -100,14 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: 3.0.0.rc1
103
+ version: 3.0.0
104
104
  required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - ">="
107
107
  - !ruby/object:Gem::Version
108
108
  version: '0'
109
109
  requirements: []
110
- rubygems_version: 3.2.2
110
+ rubygems_version: 3.2.3
111
111
  signing_key:
112
112
  specification_version: 4
113
113
  summary: The Event Library that designed for Ruby 3.0 Fiber Scheluer.