evt 0.2.3 → 0.3.4

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: bcbae12e6f731dbbd29a341c60bbb41ef1fabb6095b0bc581f1bce0a51d6a729
4
- data.tar.gz: 46497ea730a052a6c8e0a390fa031e8b81dd7b2eb84f05152e85df286db9f20a
3
+ metadata.gz: 76ad8c49742b3360c317120ecd68d3eadf7f5f654a5dd37b45cf4b5d0b59d75c
4
+ data.tar.gz: 33ac090cef3e427c7e6e2e6fb21f7eb5741ce5d3827542980727cce3d7e8eeb3
5
5
  SHA512:
6
- metadata.gz: 782544e4c98db9600fbacdeef1fdf85748494ed75e7ca4edbc154d483bf09da6ce4ff764600695368e6b7e47c26c0415523d303f74f1174b8c6c41e5130ac6b7
7
- data.tar.gz: e763ec572a4289c1c14ed83503502ffa4ffd346a239e30510fe699eb46e72cbf5fad07e67caa3f8b416a5a42c2851bc483cc81d418e9dd02550afac39d8751a2
6
+ metadata.gz: 5781576a0d1a8e2c43117b1cce3569dd13b4141bcab4523556ba1d28fda071f3fe819e24bee0fe3578541a9d3450112b7e6246e0cbb5f1ebe9345edc72e98c24
7
+ data.tar.gz: fe5cd53d17c3df49bb169f759b66b1a7119ef649445c55270ead245069a5c6c242bdf44fde8e234552bcfc8f90ac7eafde7539198af2c1ef64e0d478e2ffb67f
@@ -0,0 +1,21 @@
1
+ name: Build
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches:
6
+ - master
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-20.04
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: ruby/setup-ruby@master
13
+ with:
14
+ ruby-version: '3.0'
15
+ bundler-cache: false
16
+ - name: Install Dependencies
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs 4 --retry 3
20
+ - name: Build
21
+ run: rake build
@@ -37,18 +37,4 @@ jobs:
37
37
  DISABLE_KQUEUE: ${{ matrix.disable-kqueue }}
38
38
  run: rake compile
39
39
  - name: Test
40
- run: rake
41
- build:
42
- runs-on: ubuntu-20.04
43
- steps:
44
- - uses: actions/checkout@v2
45
- - uses: ruby/setup-ruby@master
46
- with:
47
- ruby-version: '3.0'
48
- bundler-cache: false
49
- - name: Install Dependencies
50
- run: |
51
- gem install bundler
52
- bundle install --jobs 4 --retry 3
53
- - name: Build
54
- run: gem build evt.gemspec
40
+ run: rake test
data/README.md CHANGED
@@ -4,7 +4,8 @@ The Event Library that designed for Ruby 3.0 Fiber Scheduler.
4
4
 
5
5
  **This gem is still under development, APIs and features are not stable. Advices and PRs are highly welcome.**
6
6
 
7
- [![CI Tests](https://github.com/dsh0416/evt/workflows/CI%20Tests/badge.svg)](https://github.com/dsh0416/evt/actions?query=workflow%3A%22CI+Tests%22)
7
+ [![CI Tests](https://github.com/dsh0416/evt/workflows/CI%20Tests/badge.svg)](https://github.com/dsh0416/evt/actions?query=workflow%3A%22Build%22)
8
+ [![Build](https://github.com/dsh0416/evt/workflows/Build/badge.svg)](https://github.com/dsh0416/evt/actions?query=workflow%3A%22CI+Tests%22)
8
9
  [![Gem Version](https://badge.fury.io/rb/evt.svg)](https://rubygems.org/gems/evt)
9
10
  [![Downloads](https://ruby-gem-downloads-badge.herokuapp.com/evt?type=total)](https://rubygems.org/gems/evt)
10
11
 
@@ -14,33 +15,34 @@ The Event Library that designed for Ruby 3.0 Fiber Scheduler.
14
15
 
15
16
  | | Linux | Windows | macOS | FreeBSD |
16
17
  | --------------- | ----------- | ------------| ----------- | ----------- |
17
- | io_uring | (See 1) | ❌ | ❌ | ❌ |
18
+ | io_uring | ⚠️ (See 1) | ❌ | ❌ | ❌ |
18
19
  | epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
19
20
  | kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
20
21
  | IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
21
22
  | Ruby (`IO.select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
22
23
 
23
- 1. when liburing is installed
24
- 2. when kernel version >= 2.6.8
24
+ 1. when liburing is installed. (Currently fixing)
25
+ 2. when kernel version >= 2.6.9
25
26
  3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
26
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
28
  5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
28
29
 
29
30
  ### Benchmark
30
31
 
31
- 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.
32
33
 
33
34
  The test command is `wrk -t4 -c8192 -d30s http://localhost:3001`.
34
35
 
35
36
  All of the systems have set their file descriptor limit to maximum.
37
+ On systems raising "Fiber unable to allocate memory", `sudo sysctl -w vm.max_map_count=1000000` is set.
36
38
 
37
- | OS | CPU | Memory | Backend | req/s |
38
- | ----- | ----------- | ------ | ---------------------- | -------- |
39
- | Linux | Ryzen 2700x | 64GB | epoll | 54680.08 |
40
- | Linux | Ryzen 2700x | 64GB | io_uring | 50245.53 |
41
- | Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 44159.23 |
42
- | macOS | i7-6820HQ | 16GB | kqueue | 37855.53 |
43
- | macOS | i7-6820HQ | 16GB | IO.select (using poll) | 28293.36 |
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 |
44
46
 
45
47
  ## Install
46
48
 
@@ -69,8 +71,6 @@ Fiber.schedule do
69
71
  wr.close
70
72
  end
71
73
 
72
- scheduler.run
73
-
74
74
  # "Hello World"
75
75
  ```
76
76
 
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ Rake::TestTask.new(:test) do |t|
11
11
  t.libs << "test"
12
12
  t.libs << "lib"
13
13
  t.test_files = FileList["test/**/*_test.rb"]
14
+ t.verbose = true
14
15
  end
15
16
 
16
17
  task :default => :test
@@ -18,10 +18,12 @@ Gem::Specification.new do |spec|
18
18
  # Specify which files should be added to the gem when it is released.
19
19
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
20
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|.vscode)/}) }
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|examples|.vscode)/}) }
22
22
  end
23
23
  spec.require_paths = ["lib"]
24
24
  spec.extensions = ['ext/evt/extconf.rb']
25
25
 
26
26
  spec.add_development_dependency 'rake-compiler', '~> 1.0'
27
+ spec.add_development_dependency 'simplecov', '~> 0.20.0'
28
+ spec.add_development_dependency 'minitest-reporters', '~> 1.4'
27
29
  end
@@ -1,14 +1,15 @@
1
1
  #ifndef EPOLL_H
2
- #define EPOLL_G
2
+ #define EPOLL_H
3
3
  #include "evt.h"
4
4
 
5
5
  #if HAVE_SYS_EPOLL_H
6
- VALUE method_scheduler_init(VALUE self) {
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;
9
10
  }
10
11
 
11
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
12
+ VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest) {
12
13
  struct epoll_event event;
13
14
  ID id_fileno = rb_intern("fileno");
14
15
  int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
@@ -31,15 +32,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
31
32
  return Qnil;
32
33
  }
33
34
 
34
- VALUE method_scheduler_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
- VALUE method_scheduler_wait(VALUE self) {
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;
45
38
  ID id_next_timeout = rb_intern("next_timeout");
@@ -82,7 +75,15 @@ VALUE method_scheduler_wait(VALUE self) {
82
75
  return result;
83
76
  }
84
77
 
85
- VALUE method_scheduler_backend(VALUE klass) {
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
+
86
+ VALUE method_scheduler_epoll_backend(VALUE klass) {
86
87
  return rb_str_new_cstr("epoll");
87
88
  }
88
89
  #endif
@@ -9,31 +9,37 @@ void Init_evt_ext()
9
9
  rb_ext_ractor_safe(true);
10
10
  #endif
11
11
  Evt = rb_define_module("Evt");
12
- Scheduler = rb_define_class_under(Evt, "Scheduler", rb_cObject);
13
- Payload = rb_define_class_under(Scheduler, "Payload", rb_cObject);
12
+ Bundled = rb_define_class_under(Evt, "Bundled", rb_cObject);
13
+ Payload = rb_define_class_under(Bundled, "Payload", rb_cObject);
14
14
  Fiber = rb_define_class("Fiber", rb_cObject);
15
- rb_define_singleton_method(Scheduler, "backend", method_scheduler_backend, 0);
16
- rb_define_method(Scheduler, "init_selector", method_scheduler_init, 0);
17
- rb_define_method(Scheduler, "register", method_scheduler_register, 2);
18
- rb_define_method(Scheduler, "deregister", method_scheduler_deregister, 1);
19
- rb_define_method(Scheduler, "wait", method_scheduler_wait, 0);
20
-
21
- #if HAVELIBURING_H
22
- rb_define_method(Scheduler, "io_read", method_scheduler_io_read, 4);
23
- rb_define_method(Scheduler, "io_write", method_scheduler_io_write, 4);
15
+ #if HAVE_LIBURING_H
16
+ rb_define_singleton_method(Bundled, "uring_backend", method_scheduler_uring_backend, 0);
17
+ rb_define_method(Bundled, "uring_init_selector", method_scheduler_uring_init, 0);
18
+ rb_define_method(Bundled, "uring_register", method_scheduler_uring_register, 2);
19
+ rb_define_method(Bundled, "uring_wait", method_scheduler_uring_wait, 0);
20
+ rb_define_method(Bundled, "uring_io_read", method_scheduler_uring_io_read, 4);
21
+ rb_define_method(Bundled, "uring_io_write", method_scheduler_uring_io_write, 4);
22
+ #endif
23
+ #if HAVE_SYS_EPOLL_H
24
+ rb_define_singleton_method(Bundled, "epoll_backend", method_scheduler_epoll_backend, 0);
25
+ rb_define_method(Bundled, "epoll_init_selector", method_scheduler_epoll_init, 0);
26
+ rb_define_method(Bundled, "epoll_register", method_scheduler_epoll_register, 2);
27
+ rb_define_method(Bundled, "epoll_deregister", method_scheduler_epoll_deregister, 1);
28
+ rb_define_method(Bundled, "epoll_wait", method_scheduler_epoll_wait, 0);
24
29
  #endif
30
+ #if HAVE_SYS_EVENT_H
31
+ rb_define_singleton_method(Bundled, "kqueue_backend", method_scheduler_kqueue_backend, 0);
32
+ rb_define_method(Bundled, "kqueue_init_selector", method_scheduler_kqueue_init, 0);
33
+ rb_define_method(Bundled, "kqueue_register", method_scheduler_kqueue_register, 2);
34
+ rb_define_method(Bundled, "kqueue_wait", method_scheduler_kqueue_wait, 0);
35
+ #endif
36
+ rb_define_singleton_method(Bundled, "select_backend", method_scheduler_select_backend, 0);
37
+ rb_define_method(Bundled, "select_wait", method_scheduler_select_wait, 0);
25
38
  }
26
39
 
27
- #if HAVE_LIBURING_H
28
- #include "uring.h"
29
- #elif HAVE_SYS_EPOLL_H
30
- #include "epoll.h"
31
- #elif HAVE_SYS_EVENT_H
32
- #include "kqueue.h"
33
- #elif HAVE_WINDOWS_H
34
- #include "select.h"
35
- // #include "iocp.h"
36
- #else
37
- #include "select.h"
38
- #endif
40
+ #include "uring.h"
41
+ #include "epoll.h"
42
+ #include "kqueue.h"
43
+ // #include "iocp.h"
44
+ #include "select.h"
39
45
  #endif
@@ -4,29 +4,21 @@
4
4
  #include <ruby.h>
5
5
 
6
6
  VALUE Evt = Qnil;
7
- VALUE Scheduler = Qnil;
7
+ VALUE Bundled = Qnil;
8
8
  VALUE Payload = Qnil;
9
9
  VALUE Fiber = Qnil;
10
10
 
11
11
  void Init_evt_ext();
12
- VALUE method_scheduler_init(VALUE self);
13
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest);
14
- VALUE method_scheduler_deregister(VALUE self, VALUE io);
15
- VALUE method_scheduler_wait(VALUE self);
16
- VALUE method_scheduler_backend(VALUE klass);
17
- #if HAVE_LIBURING_H
18
- VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
19
- VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
20
- #endif
21
-
22
- #if HAV_WINDOWS_H
23
- VALUE method_scheduler_io_read(VALUE io, VALUE buffer, VALUE offset, VALUE length);
24
- VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE length);
25
- #endif
26
12
 
27
13
  #if HAVE_LIBURING_H
28
- #include <liburing.h>
14
+ VALUE method_scheduler_uring_init(VALUE self);
15
+ VALUE method_scheduler_uring_register(VALUE self, VALUE io, VALUE interest);
16
+ VALUE method_scheduler_uring_wait(VALUE self);
17
+ VALUE method_scheduler_uring_backend(VALUE klass);
18
+ VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
19
+ VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
29
20
 
21
+ #include <liburing.h>
30
22
  #define URING_ENTRIES 64
31
23
  #define URING_MAX_EVENTS 64
32
24
 
@@ -49,13 +41,25 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
49
41
  .data = NULL,
50
42
  .flags = RUBY_TYPED_FREE_IMMEDIATELY,
51
43
  };
52
- #elif HAVE_SYS_EPOLL_H
44
+ #endif
45
+ #if HAVE_SYS_EPOLL_H
46
+ VALUE method_scheduler_epoll_init(VALUE self);
47
+ VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest);
48
+ VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io);
49
+ VALUE method_scheduler_epoll_wait(VALUE self);
50
+ VALUE method_scheduler_epoll_backend(VALUE klass);
53
51
  #include <sys/epoll.h>
54
52
  #define EPOLL_MAX_EVENTS 64
55
- #elif HAVE_SYS_EVENT_H
53
+ #endif
54
+ #if HAVE_SYS_EVENT_H
55
+ VALUE method_scheduler_kqueue_init(VALUE self);
56
+ VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest);
57
+ VALUE method_scheduler_kqueue_wait(VALUE self);
58
+ VALUE method_scheduler_kqueue_backend(VALUE klass);
56
59
  #include <sys/event.h>
57
60
  #define KQUEUE_MAX_EVENTS 64
58
- #elif HAVE_WINDOWS_H
61
+ #endif
62
+ #if HAVE_WINDOWS_H
59
63
  // #include <Windows.h>
60
64
  // #define IOCP_MAX_EVENTS 64
61
65
 
@@ -79,4 +83,6 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
79
83
  // .flags = RUBY_TYPED_FREE_IMMEDIATELY,
80
84
  // };
81
85
  #endif
86
+ VALUE method_scheduler_select_wait(VALUE self);
87
+ VALUE method_scheduler_select_backend(VALUE klass);
82
88
  #endif
@@ -11,13 +11,13 @@ size_t iocp_payload_size(const void* data) {
11
11
  return sizeof(HANDLE);
12
12
  }
13
13
 
14
- VALUE method_scheduler_init(VALUE self) {
14
+ VALUE method_scheduler_iocp_init(VALUE self) {
15
15
  HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
16
16
  rb_iv_set(self, "@iocp", TypedData_Wrap_Struct(Payload, &type_iocp_payload, iocp));
17
17
  return Qnil;
18
18
  }
19
19
 
20
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
20
+ VALUE method_scheduler_iocp_register(VALUE self, VALUE io, VALUE interest) {
21
21
  HANDLE iocp;
22
22
  VALUE iocp_obj = rb_iv_get(self, "@iocp");
23
23
  struct iocp_data* data;
@@ -47,11 +47,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
47
47
  return Qnil;
48
48
  }
49
49
 
50
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
51
- return Qnil;
52
- }
53
-
54
- VALUE method_scheduler_wait(VALUE self) {
50
+ VALUE method_scheduler_iocp_wait(VALUE self) {
55
51
  ID id_next_timeout = rb_intern("next_timeout");
56
52
  ID id_push = rb_intern("push");
57
53
  VALUE iocp_obj = rb_iv_get(self, "@iocp");
@@ -119,7 +115,7 @@ VALUE method_scheduler_wait(VALUE self) {
119
115
  return result;
120
116
  }
121
117
 
122
- VALUE method_scheduler_backend(VALUE klass) {
118
+ VALUE method_scheduler_iocp_backend(VALUE klass) {
123
119
  return rb_str_new_cstr("iocp");
124
120
  }
125
121
  #endif
@@ -4,12 +4,12 @@
4
4
 
5
5
  #if HAVE_SYS_EVENT_H
6
6
 
7
- VALUE method_scheduler_init(VALUE self) {
7
+ VALUE method_scheduler_kqueue_init(VALUE self) {
8
8
  rb_iv_set(self, "@kq", INT2NUM(kqueue()));
9
9
  return Qnil;
10
10
  }
11
11
 
12
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
12
+ VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest) {
13
13
  struct kevent event;
14
14
  u_short event_flags = 0;
15
15
  ID id_fileno = rb_intern("fileno");
@@ -32,12 +32,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
32
32
  return Qnil;
33
33
  }
34
34
 
35
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
36
- // One-shot mode
37
- return Qnil;
38
- }
39
-
40
- VALUE method_scheduler_wait(VALUE self) {
35
+ VALUE method_scheduler_kqueue_wait(VALUE self) {
41
36
  int n, kq, i;
42
37
  u_short event_flags = 0;
43
38
  struct kevent events[KQUEUE_MAX_EVENTS];
@@ -80,7 +75,7 @@ VALUE method_scheduler_wait(VALUE self) {
80
75
  return result;
81
76
  }
82
77
 
83
- VALUE method_scheduler_backend(VALUE klass) {
78
+ VALUE method_scheduler_kqueue_backend(VALUE klass) {
84
79
  return rb_str_new_cstr("kqueue");
85
80
  }
86
81
  #endif
@@ -2,19 +2,7 @@
2
2
  #define SELECT_H
3
3
  #include "evt.h"
4
4
 
5
- VALUE method_scheduler_init(VALUE self) {
6
- return Qnil;
7
- }
8
-
9
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
10
- return Qnil;
11
- }
12
-
13
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
14
- return Qnil;
15
- }
16
-
17
- VALUE method_scheduler_wait(VALUE self) {
5
+ VALUE method_scheduler_select_wait(VALUE self) {
18
6
  // return IO.select(@readable.keys, @writable.keys, [], next_timeout)
19
7
  VALUE readable, writable, readable_keys, writable_keys, next_timeout;
20
8
  ID id_select = rb_intern("select");
@@ -30,7 +18,7 @@ VALUE method_scheduler_wait(VALUE self) {
30
18
  return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
31
19
  }
32
20
 
33
- VALUE method_scheduler_backend(VALUE klass) {
21
+ VALUE method_scheduler_select_backend(VALUE klass) {
34
22
  return rb_str_new_cstr("ruby");
35
23
  }
36
24
  #endif
@@ -12,7 +12,7 @@ size_t uring_payload_size(const void* data) {
12
12
  return sizeof(struct io_uring);
13
13
  }
14
14
 
15
- VALUE method_scheduler_init(VALUE self) {
15
+ VALUE method_scheduler_uring_init(VALUE self) {
16
16
  int ret;
17
17
  struct io_uring* ring;
18
18
  ring = xmalloc(sizeof(struct io_uring));
@@ -24,7 +24,7 @@ VALUE method_scheduler_init(VALUE self) {
24
24
  return Qnil;
25
25
  }
26
26
 
27
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
27
+ VALUE method_scheduler_uring_register(VALUE self, VALUE io, VALUE interest) {
28
28
  VALUE ring_obj;
29
29
  struct io_uring* ring;
30
30
  struct io_uring_sqe *sqe;
@@ -60,12 +60,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
60
60
  return Qnil;
61
61
  }
62
62
 
63
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
64
- // io_uring runs under oneshot mode. No need to deregister.
65
- return Qnil;
66
- }
67
-
68
- VALUE method_scheduler_wait(VALUE self) {
63
+ VALUE method_scheduler_uring_wait(VALUE self) {
69
64
  struct io_uring* ring;
70
65
  struct io_uring_cqe *cqes[URING_MAX_EVENTS];
71
66
  struct uring_data *data;
@@ -84,6 +79,12 @@ VALUE method_scheduler_wait(VALUE self) {
84
79
  iovs = rb_ary_new();
85
80
 
86
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);
87
88
  ret = io_uring_peek_batch_cqe(ring, cqes, URING_MAX_EVENTS);
88
89
 
89
90
  for (i = 0; i < ret; i++) {
@@ -99,21 +100,14 @@ VALUE method_scheduler_wait(VALUE self) {
99
100
  rb_funcall(writables, id_push, 1, obj_io);
100
101
  }
101
102
  } else {
102
- 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));
103
107
  }
104
108
  io_uring_cqe_seen(ring, cqes[i]);
105
109
  }
106
110
 
107
- if (ret == 0) {
108
- if (next_timeout != Qnil && NUM2INT(next_timeout) != -1) {
109
- // sleep
110
- time = next_timeout / 1000;
111
- rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(time));
112
- } else {
113
- rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(0.001)); // To avoid infinite loop
114
- }
115
- }
116
-
117
111
  result = rb_ary_new2(3);
118
112
  rb_ary_store(result, 0, readables);
119
113
  rb_ary_store(result, 1, writables);
@@ -122,7 +116,7 @@ VALUE method_scheduler_wait(VALUE self) {
122
116
  return result;
123
117
  }
124
118
 
125
- VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
119
+ VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
126
120
  struct io_uring* ring;
127
121
  struct uring_data *data;
128
122
  char* read_buffer;
@@ -137,30 +131,26 @@ VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset,
137
131
  int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
138
132
 
139
133
  read_buffer = (char*) xmalloc(NUM2SIZET(length));
140
- struct iovec iov = {
141
- .iov_base = read_buffer,
142
- .iov_len = NUM2SIZET(length),
143
- };
144
134
 
145
135
  data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
146
136
  data->is_poll = false;
147
137
  data->io = io;
148
138
  data->poll_mask = 0;
149
139
 
150
- io_uring_prep_readv(sqe, fd, &iov, 1, NUM2SIZET(offset));
140
+ io_uring_prep_read(sqe, fd, read_buffer, 1, NUM2SIZET(length), NUM2SIZET(offset));
151
141
  io_uring_sqe_set_data(sqe, data);
152
142
  io_uring_submit(ring);
153
143
 
144
+ VALUE ret = rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
145
+
154
146
  VALUE result = rb_str_new(read_buffer, strlen(read_buffer));
155
147
  if (buffer != Qnil) {
156
148
  rb_str_append(buffer, result);
157
149
  }
158
-
159
- rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
160
- return result;
150
+ return ret;
161
151
  }
162
152
 
163
- VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
153
+ VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
164
154
  struct io_uring* ring;
165
155
  struct uring_data *data;
166
156
  char* write_buffer;
@@ -175,24 +165,19 @@ VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset
175
165
  int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
176
166
 
177
167
  write_buffer = StringValueCStr(buffer);
178
- struct iovec iov = {
179
- .iov_base = write_buffer,
180
- .iov_len = NUM2SIZET(length),
181
- };
182
168
 
183
169
  data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
184
170
  data->is_poll = false;
185
171
  data->io = io;
186
172
  data->poll_mask = 0;
187
173
 
188
- io_uring_prep_writev(sqe, fd, &iov, 1, NUM2SIZET(offset));
174
+ io_uring_prep_write(sqe, fd, write_buffer, NUM2SIZET(length), NUM2SIZET(offset));
189
175
  io_uring_sqe_set_data(sqe, data);
190
176
  io_uring_submit(ring);
191
- rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
192
- return length;
177
+ return rb_funcall(Fiber, rb_intern("yield"), 0);
193
178
  }
194
179
 
195
- VALUE method_scheduler_backend(VALUE klass) {
180
+ VALUE method_scheduler_uring_backend(VALUE klass) {
196
181
  return rb_str_new_cstr("liburing");
197
182
  }
198
183
 
data/lib/evt.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fiber'
4
+ require 'socket'
5
+ require 'io/nonblock'
3
6
  require_relative 'evt/version'
7
+ require_relative 'evt/backends/bundled'
8
+ require_relative 'evt/backends/epoll'
9
+ require_relative 'evt/backends/iocp'
10
+ require_relative 'evt/backends/kqueue'
11
+ require_relative 'evt/backends/select'
12
+ require_relative 'evt/backends/uring'
4
13
  require_relative 'evt/scheduler'
5
14
  require_relative 'evt_ext'
6
15
 
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Bundled
4
+ MAXIMUM_TIMEOUT = 5000
5
+ COLLECT_COUNTER_MAX = 16384
6
+
7
+ def initialize
8
+ @readable = {}
9
+ @writable = {}
10
+ @waiting = {}
11
+ @iovs = {}
12
+
13
+ @lock = Mutex.new
14
+ @blocking = 0
15
+ @ready = []
16
+ @collect_counter = 0
17
+
18
+ init_selector
19
+ end
20
+
21
+ attr_reader :readable
22
+ attr_reader :writable
23
+ attr_reader :waiting
24
+
25
+ def next_timeout
26
+ _fiber, timeout = @waiting.min_by{ |key, value| value }
27
+
28
+ if timeout
29
+ offset = (timeout - current_time) * 1000 # Use mililisecond
30
+ return 0 if offset < 0
31
+ return offset if offset < MAXIMUM_TIMEOUT
32
+ end
33
+
34
+ MAXIMUM_TIMEOUT
35
+ end
36
+
37
+ def run
38
+ while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @blocking.positive?
39
+ readable, writable, iovs = self.wait
40
+
41
+ readable&.each do |io|
42
+ fiber = @readable.delete(io)
43
+ fiber&.resume
44
+ end
45
+
46
+ writable&.each do |io|
47
+ fiber = @writable.delete(io)
48
+ fiber&.resume
49
+ end
50
+
51
+ unless iovs.nil?
52
+ iovs&.each do |v|
53
+ io, ret = v
54
+ fiber = @iovs.delete(io)
55
+ fiber&.resume(ret)
56
+ end
57
+ end
58
+
59
+ collect
60
+
61
+ if @waiting.any?
62
+ time = current_time
63
+ waiting, @waiting = @waiting, {}
64
+
65
+ waiting.each do |fiber, timeout|
66
+ if timeout <= time
67
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
68
+ else
69
+ @waiting[fiber] = timeout
70
+ end
71
+ end
72
+ end
73
+
74
+ if @ready.any?
75
+ ready = nil
76
+
77
+ @lock.synchronize do
78
+ ready, @ready = @ready, []
79
+ end
80
+
81
+ ready.each do |fiber|
82
+ fiber.resume if fiber.is_a? Fiber and fiber.alive?
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def current_time
89
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
90
+ end
91
+
92
+ # Wait for the given file descriptor to match the specified events within
93
+ # the specified timeout.
94
+ # @parameter event [Integer] A bit mask of `IO::READABLE`,
95
+ # `IO::WRITABLE` and `IO::PRIORITY`.
96
+ # @parameter timeout [Numeric] The amount of time to wait for the event in seconds.
97
+ # @returns [Integer] The subset of events that are ready.
98
+ def io_wait(io, events, duration)
99
+ # TODO: IO::PRIORITY
100
+ @readable[io] = Fiber.current unless (events & IO::READABLE).zero?
101
+ @writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
102
+ self.register(io, events)
103
+ Fiber.yield
104
+ self.deregister(io)
105
+ true
106
+ end
107
+
108
+ # Sleep the current task for the specified duration, or forever if not
109
+ # specified.
110
+ # @param duration [Numeric] The amount of time to sleep in seconds.
111
+ def kernel_sleep(duration = nil)
112
+ self.block(:sleep, duration)
113
+ true
114
+ end
115
+
116
+ # Block the calling fiber.
117
+ # @parameter blocker [Object] What we are waiting on, informational only.
118
+ # @parameter timeout [Numeric | Nil] The amount of time to wait for in seconds.
119
+ # @returns [Boolean] Whether the blocking operation was successful or not.
120
+ def block(blocker, timeout = nil)
121
+ if timeout
122
+ @waiting[Fiber.current] = current_time + timeout
123
+ begin
124
+ Fiber.yield
125
+ ensure
126
+ @waiting.delete(Fiber.current)
127
+ end
128
+ else
129
+ @blocking += 1
130
+ begin
131
+ Fiber.yield
132
+ ensure
133
+ @blocking -= 1
134
+ end
135
+ end
136
+ end
137
+
138
+ # Unblock the specified fiber.
139
+ # @parameter blocker [Object] What we are waiting on, informational only.
140
+ # @parameter fiber [Fiber] The fiber to unblock.
141
+ # @reentrant Thread safe.
142
+ def unblock(blocker, fiber)
143
+ @lock.synchronize do
144
+ @ready << fiber
145
+ end
146
+ end
147
+
148
+ # Invoked when the thread exits.
149
+ def close
150
+ self.run
151
+ end
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
+
175
+ # Intercept the creation of a non-blocking fiber.
176
+ # @returns [Fiber]
177
+ def fiber(&block)
178
+ fiber = Fiber.new(blocking: false, &block)
179
+ fiber.resume
180
+ fiber
181
+ end
182
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Epoll < Evt::Bundled
4
+ def self.available?
5
+ self.respond_to?(:epoll_backend)
6
+ end
7
+
8
+ def self.backend
9
+ self.epoll_backend
10
+ end
11
+
12
+ def init_selector
13
+ epoll_init_selector
14
+ end
15
+
16
+ def register(io, interest)
17
+ epoll_register(io, interest)
18
+ end
19
+
20
+ def deregister(io)
21
+ epoll_deregister(io)
22
+ end
23
+
24
+ def wait
25
+ epoll_wait
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Iocp < Evt::Bundled
4
+ ##
5
+ # IOCP is totally disabled for now
6
+ def self.available?
7
+ false
8
+ end
9
+
10
+ def init_selector
11
+ # Placeholder
12
+ end
13
+
14
+ def register(io, interest)
15
+ # Placeholder
16
+ end
17
+
18
+ def deregister(io)
19
+ end
20
+
21
+ def io_read(io, buffer, offset, length)
22
+ # Placeholder
23
+ end
24
+
25
+ def io_write(io, buffer, offset, length)
26
+ # Placeholder
27
+ end
28
+
29
+ def wait
30
+ # Placeholder
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Kqueue < Evt::Bundled
4
+ def self.available?
5
+ self.respond_to?(:kqueue_backend)
6
+ end
7
+
8
+ def self.backend
9
+ self.kqueue_backend
10
+ end
11
+
12
+ def init_selector
13
+ kqueue_init_selector
14
+ end
15
+
16
+ def register(io, interest)
17
+ kqueue_register(io, interest)
18
+ end
19
+
20
+ def deregister(io)
21
+ end
22
+
23
+ def wait
24
+ kqueue_wait
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Select < Evt::Bundled
4
+ def self.available?
5
+ self.respond_to?(:select_backend)
6
+ end
7
+
8
+ def self.backend
9
+ self.select_backend
10
+ end
11
+
12
+ def init_selector
13
+ # Select is stateless
14
+ end
15
+
16
+ def register(io, interest)
17
+ # Select is stateless
18
+ end
19
+
20
+ def deregister(io)
21
+ end
22
+
23
+ def wait
24
+ select_wait
25
+ rescue Errno::EBADF => _
26
+ collect(true)
27
+ return [], []
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Evt::Uring < Evt::Bundled
4
+ def self.available?
5
+ self.respond_to?(:uring_backend)
6
+ end
7
+
8
+ def self.backend
9
+ self.uring_backend
10
+ end
11
+
12
+ def init_selector
13
+ uring_init_selector
14
+ end
15
+
16
+ def register(io, interest)
17
+ uring_register(io, interest)
18
+ end
19
+
20
+ def deregister(io)
21
+ end
22
+
23
+ def io_read(io, buffer, offset, length)
24
+ uring_io_read(io, buffer, offset, length)
25
+ end
26
+
27
+ def io_write(io, buffer, offset, length)
28
+ uring_io_write(io, buffer, offset, length)
29
+ end
30
+
31
+ def wait
32
+ uring_wait
33
+ end
34
+ end
@@ -1,121 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fiber'
4
- require 'socket'
5
- require 'io/nonblock'
6
-
3
+ ##
4
+ # The major class for Ruby Fiber Scheduler
5
+ # @example
6
+ # scheduler = Evt::Scheduler.new
7
+ # Fiber.set_scheduler scheduler
8
+ # scheduler.run
7
9
  class Evt::Scheduler
8
- def initialize
9
- @readable = {}
10
- @writable = {}
11
- @iovs = {}
12
- @waiting = {}
13
-
14
- @lock = Mutex.new
15
- @locking = 0
16
- @ready = []
17
-
18
- @ios = ObjectSpace::WeakMap.new
19
- init_selector
20
- end
21
-
22
- attr_reader :readable
23
- attr_reader :writable
24
- attr_reader :waiting
25
-
26
- def next_timeout
27
- _fiber, timeout = @waiting.min_by{|key, value| value}
28
-
29
- if timeout
30
- offset = timeout - current_time
31
- offset < 0 ? 0 : offset
32
- end
33
- end
34
-
35
- def run
36
- while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @locking.positive?
37
- readable, writable, iovs = self.wait
38
-
39
- readable&.each do |io|
40
- fiber = @readable.delete(io)
41
- fiber&.resume
42
- end
43
-
44
- writable&.each do |io|
45
- fiber = @writable.delete(io)
46
- fiber&.resume
47
- end
48
-
49
- unless iovs.nil?
50
- iovs&.each do |io|
51
- fiber = @iovs.delete(io)
52
- fiber&.resume
53
- end
54
- end
55
-
56
- if @waiting.any?
57
- time = current_time
58
- waiting = @waiting
59
- @waiting = {}
60
-
61
- waiting.each do |fiber, timeout|
62
- if timeout <= time
63
- fiber.resume
64
- else
65
- @waiting[fiber] = timeout
66
- end
67
- end
68
- end
69
-
70
- if @ready.any?
71
- ready = nil
72
-
73
- @lock.synchronize do
74
- ready, @ready = @ready, []
75
- end
76
-
77
- ready.each do |fiber|
78
- fiber.resume
79
- end
10
+ class << self
11
+ BACKENDS = [
12
+ Evt::Uring,
13
+ Evt::Epoll,
14
+ Evt::Kqueue,
15
+ Evt::Iocp,
16
+ Evt::Select,
17
+ ].freeze
18
+
19
+ ##
20
+ # Returns the fastest possible scheduler
21
+ # Use the backend scheduler directly if you want to choose it yourself
22
+ def new
23
+ BACKENDS.each do |backend|
24
+ return backend.new if backend.available?
80
25
  end
81
26
  end
82
- end
83
-
84
- def current_time
85
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
86
- end
87
-
88
- def io_wait(io, events, duration)
89
- @readable[io] = Fiber.current unless (events & IO::READABLE).zero?
90
- @writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
91
- self.register(io, events)
92
- Fiber.yield
93
- self.deregister(io)
94
- true
95
- end
96
-
97
- def kernel_sleep(duration = nil)
98
- @waiting[Fiber.current] = current_time + duration if duration.nil?
99
- Fiber.yield
100
- true
101
- end
102
-
103
- def mutex_lock(mutex)
104
- @locking += 1
105
- Fiber.yield
106
- ensure
107
- @locking -= 1
108
- end
109
27
 
110
- def mutex_unlock(mutex, fiber)
111
- @lock.synchronize do
112
- @ready << fiber
28
+ ##
29
+ # Returns all available backends on this machine
30
+ def availables
31
+ BACKENDS.filter do |backend|
32
+ backend.available?
33
+ end
113
34
  end
114
35
  end
115
-
116
- def fiber(&block)
117
- fiber = Fiber.new(blocking: false, &block)
118
- fiber.resume
119
- fiber
120
- end
121
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evt
4
- VERSION = "0.2.3"
4
+ VERSION = "0.3.4"
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.2.3
4
+ version: 0.3.4
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
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.20.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
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'
27
55
  description: A low-level Event Handler designed for Ruby 3 Scheduler for better performance
28
56
  email:
29
57
  - dsh0416@gmail.com
@@ -32,6 +60,7 @@ extensions:
32
60
  - ext/evt/extconf.rb
33
61
  extra_rdoc_files: []
34
62
  files:
63
+ - ".github/workflows/build.yml"
35
64
  - ".github/workflows/test.yml"
36
65
  - ".gitignore"
37
66
  - CODE_OF_CONDUCT.md
@@ -49,6 +78,12 @@ files:
49
78
  - ext/evt/select.h
50
79
  - ext/evt/uring.h
51
80
  - lib/evt.rb
81
+ - lib/evt/backends/bundled.rb
82
+ - lib/evt/backends/epoll.rb
83
+ - lib/evt/backends/iocp.rb
84
+ - lib/evt/backends/kqueue.rb
85
+ - lib/evt/backends/select.rb
86
+ - lib/evt/backends/uring.rb
52
87
  - lib/evt/scheduler.rb
53
88
  - lib/evt/version.rb
54
89
  homepage: https://github.com/dsh0416/evt
@@ -57,7 +92,7 @@ licenses:
57
92
  metadata:
58
93
  homepage_uri: https://github.com/dsh0416/evt
59
94
  source_code_uri: https://github.com/dsh0416/evt
60
- post_install_message:
95
+ post_install_message:
61
96
  rdoc_options: []
62
97
  require_paths:
63
98
  - lib
@@ -73,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
108
  version: '0'
74
109
  requirements: []
75
110
  rubygems_version: 3.2.2
76
- signing_key:
111
+ signing_key:
77
112
  specification_version: 4
78
113
  summary: The Event Library that designed for Ruby 3.0 Fiber Scheluer.
79
114
  test_files: []