evt 0.3.2 → 0.4.0

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